Container Memory Dump Analysis for Forensics: Enterprise Investigation Guide
Master container memory dump analysis for forensic investigations with comprehensive techniques for capturing, analyzing, and extracting artifacts from container memory in enterprise Kubernetes and Docker environments.
Container Memory Dump Analysis for Forensics: Enterprise Investigation Guide
Executive Summary
Memory forensics in containerized environments presents unique challenges due to the ephemeral nature of containers, shared kernel space, and namespace isolation. This comprehensive guide covers enterprise-grade techniques for capturing and analyzing container memory dumps, extracting security artifacts, detecting malware, and reconstructing attack timelines. We’ll explore production-tested tools, automated analysis workflows, and best practices for container memory forensics in Kubernetes and Docker environments.
Understanding Container Memory Architecture
Container Memory Layout
Containers share the host kernel but have isolated memory spaces through cgroups and namespaces:
┌─────────────────────────────────────────┐
│ Host Physical Memory │
├─────────────────────────────────────────┤
│ Host Kernel Space │
│ (Shared by all containers) │
├─────────────────────────────────────────┤
│ Container 1 Memory Cgroup │
│ ┌──────────────────────────────────┐ │
│ │ Process Memory Space │ │
│ │ - Code (.text) │ │
│ │ - Data (.data, .bss) │ │
│ │ - Heap │ │
│ │ - Memory-mapped files │ │
│ │ - Stack(s) │ │
│ └──────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ Container 2 Memory Cgroup │
│ ┌──────────────────────────────────┐ │
│ │ Process Memory Space │ │
│ │ ... │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
Key Memory Artifacts in Containers
Important artifacts available in container memory:
Process Memory
- Running binaries and libraries
- Application data structures
- Configuration and credentials
- Network connection state
File Cache
- Recently accessed files
- Deleted files still in cache
- Temporary files
Network Buffers
- Active connections
- Recent network traffic
- SSL/TLS session keys
Kernel Structures
- Process lists
- Open file descriptors
- Network sockets
- Loaded kernel modules
Memory Capture Techniques
Live Container Memory Capture
Capture memory from a running container using multiple methods:
#!/bin/bash
# capture-container-memory.sh - Comprehensive container memory capture
set -euo pipefail
CONTAINER_ID="${1:-}"
OUTPUT_DIR="${2:-./memory-capture-$(date +%Y%m%d-%H%M%S)}"
if [[ -z "${CONTAINER_ID}" ]]; then
echo "Usage: $0 <container-id> [output-dir]"
exit 1
fi
mkdir -p "${OUTPUT_DIR}"
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "${OUTPUT_DIR}/capture.log"
}
log "Starting memory capture for container: ${CONTAINER_ID}"
# Determine container runtime
if command -v docker &> /dev/null && docker inspect "${CONTAINER_ID}" &> /dev/null; then
RUNTIME="docker"
RUNTIME_CMD="docker"
elif command -v podman &> /dev/null && podman inspect "${CONTAINER_ID}" &> /dev/null; then
RUNTIME="podman"
RUNTIME_CMD="podman"
elif command -v crictl &> /dev/null && crictl inspect "${CONTAINER_ID}" &> /dev/null; then
RUNTIME="containerd"
RUNTIME_CMD="crictl"
else
log "ERROR: Could not determine container runtime or container not found"
exit 1
fi
log "Container runtime: ${RUNTIME}"
# Get container information
log "Collecting container metadata..."
${RUNTIME_CMD} inspect "${CONTAINER_ID}" > "${OUTPUT_DIR}/container-inspect.json"
# Get container PID
if [[ "${RUNTIME}" == "docker" || "${RUNTIME}" == "podman" ]]; then
CONTAINER_PID=$(${RUNTIME_CMD} inspect --format '{{.State.Pid}}' "${CONTAINER_ID}")
elif [[ "${RUNTIME}" == "containerd" ]]; then
CONTAINER_PID=$(crictl inspect "${CONTAINER_ID}" | jq -r '.info.pid')
fi
log "Container main PID: ${CONTAINER_PID}"
# Get all PIDs in container namespace
log "Identifying all processes in container..."
CONTAINER_PIDS=$(nsenter -t "${CONTAINER_PID}" -p ps -eo pid --no-headers | tr '\n' ' ')
echo "${CONTAINER_PIDS}" > "${OUTPUT_DIR}/container-pids.txt"
log "Container PIDs: ${CONTAINER_PIDS}"
# METHOD 1: Use gcore to dump process memory
log "METHOD 1: Capturing process memory with gcore..."
mkdir -p "${OUTPUT_DIR}/gcore"
for pid in ${CONTAINER_PIDS}; do
log " Dumping memory for PID ${pid}..."
gcore -o "${OUTPUT_DIR}/gcore/core" "${pid}" 2>&1 | tee -a "${OUTPUT_DIR}/capture.log" || true
done
# METHOD 2: Copy process memory maps directly
log "METHOD 2: Copying process memory maps from /proc..."
mkdir -p "${OUTPUT_DIR}/proc-mem"
for pid in ${CONTAINER_PIDS}; do
log " Capturing /proc/${pid}/mem..."
# Save memory maps
cp "/proc/${pid}/maps" "${OUTPUT_DIR}/proc-mem/pid-${pid}-maps.txt" 2>/dev/null || true
cp "/proc/${pid}/status" "${OUTPUT_DIR}/proc-mem/pid-${pid}-status.txt" 2>/dev/null || true
cp "/proc/${pid}/cmdline" "${OUTPUT_DIR}/proc-mem/pid-${pid}-cmdline.txt" 2>/dev/null || true
cp "/proc/${pid}/environ" "${OUTPUT_DIR}/proc-mem/pid-${pid}-environ.txt" 2>/dev/null || true
# Dump memory segments
if [[ -r "/proc/${pid}/mem" ]]; then
mkdir -p "${OUTPUT_DIR}/proc-mem/pid-${pid}"
# Parse memory maps and dump each segment
while IFS= read -r line; do
# Extract address range and permissions
addr_range=$(echo "$line" | awk '{print $1}')
perms=$(echo "$line" | awk '{print $2}')
path=$(echo "$line" | awk '{print $6}')
# Skip non-readable segments
[[ "$perms" =~ r ]] || continue
start_addr="0x${addr_range%-*}"
end_addr="0x${addr_range#*-}"
# Calculate size
size=$((end_addr - start_addr))
# Skip if too large (> 100MB)
if [[ $size -gt 104857600 ]]; then
log " Skipping large segment: ${addr_range} (${size} bytes)"
continue
fi
segment_name=$(echo "${addr_range}_${path##*/}" | tr '/' '_')
log " Dumping segment: ${addr_range} (${size} bytes)"
dd if="/proc/${pid}/mem" of="${OUTPUT_DIR}/proc-mem/pid-${pid}/${segment_name}.bin" \
bs=1 skip=$((start_addr)) count=$size 2>/dev/null || true
done < "/proc/${pid}/maps"
fi
done
# METHOD 3: Use container checkpoint (if supported)
log "METHOD 3: Creating container checkpoint..."
if [[ "${RUNTIME}" == "docker" ]]; then
# Experimental feature in Docker
docker checkpoint create "${CONTAINER_ID}" "forensics-checkpoint-$(date +%s)" \
--checkpoint-dir="${OUTPUT_DIR}/checkpoint" 2>&1 | tee -a "${OUTPUT_DIR}/capture.log" || \
log " Docker checkpoint not available (experimental feature)"
elif [[ "${RUNTIME}" == "podman" ]]; then
podman container checkpoint "${CONTAINER_ID}" \
--export="${OUTPUT_DIR}/checkpoint/checkpoint.tar.gz" 2>&1 | tee -a "${OUTPUT_DIR}/capture.log" || \
log " Podman checkpoint failed"
fi
# METHOD 4: Capture container filesystem (includes memory-mapped files)
log "METHOD 4: Capturing container filesystem..."
mkdir -p "${OUTPUT_DIR}/filesystem"
${RUNTIME_CMD} export "${CONTAINER_ID}" | tar -C "${OUTPUT_DIR}/filesystem" -xf - 2>&1 | tee -a "${OUTPUT_DIR}/capture.log" || true
# Collect additional context
log "Collecting additional context..."
# Network connections
nsenter -t "${CONTAINER_PID}" -n netstat -tunap > "${OUTPUT_DIR}/network-connections.txt" 2>&1 || true
# Open files
for pid in ${CONTAINER_PIDS}; do
lsof -p "${pid}" > "${OUTPUT_DIR}/open-files-${pid}.txt" 2>&1 || true
done
# Process tree
nsenter -t "${CONTAINER_PID}" -p ps auxwwf > "${OUTPUT_DIR}/process-tree.txt" 2>&1 || true
# Environment variables (sanitized)
for pid in ${CONTAINER_PIDS}; do
cat "/proc/${pid}/environ" | tr '\0' '\n' > "${OUTPUT_DIR}/environment-${pid}.txt" 2>&1 || true
done
# Calculate checksums
log "Calculating checksums..."
find "${OUTPUT_DIR}" -type f -exec sha256sum {} \; > "${OUTPUT_DIR}/checksums.txt"
# Create metadata file
cat > "${OUTPUT_DIR}/METADATA.txt" << EOF
Container Memory Capture Metadata
==================================
Capture Date: $(date --iso-8601=seconds)
Capture Host: $(hostname)
Capturer: $(whoami)
Container Information:
- Container ID: ${CONTAINER_ID}
- Runtime: ${RUNTIME}
- Main PID: ${CONTAINER_PID}
- All PIDs: ${CONTAINER_PIDS}
Container Details:
$(${RUNTIME_CMD} inspect "${CONTAINER_ID}" --format '- Image: {{.Config.Image}}
- Status: {{.State.Status}}
- Started: {{.State.StartedAt}}
- Name: {{.Name}}')
Capture Methods Used:
- gcore dumps: $(ls -1 ${OUTPUT_DIR}/gcore/*.core.* 2>/dev/null | wc -l) files
- /proc/mem dumps: $(find ${OUTPUT_DIR}/proc-mem -name "*.bin" 2>/dev/null | wc -l) segments
- Checkpoint: $(if [[ -d ${OUTPUT_DIR}/checkpoint ]]; then echo "Yes"; else echo "No"; fi)
- Filesystem: $(if [[ -d ${OUTPUT_DIR}/filesystem ]]; then echo "Yes"; else echo "No"; fi)
Total Capture Size: $(du -sh "${OUTPUT_DIR}" | cut -f1)
EOF
cat "${OUTPUT_DIR}/METADATA.txt"
# Compress capture
log "Compressing memory capture..."
tar -czf "${OUTPUT_DIR}.tar.gz" "${OUTPUT_DIR}"
sha256sum "${OUTPUT_DIR}.tar.gz" > "${OUTPUT_DIR}.tar.gz.sha256"
log "Memory capture complete!"
log "Output directory: ${OUTPUT_DIR}"
log "Compressed archive: ${OUTPUT_DIR}.tar.gz"
log "Archive checksum: $(cat ${OUTPUT_DIR}.tar.gz.sha256)"
Kubernetes Pod Memory Capture
Capture memory from Kubernetes pods with proper orchestration:
#!/bin/bash
# k8s-pod-memory-capture.sh - Kubernetes pod memory capture
set -euo pipefail
POD_NAME="${1:-}"
NAMESPACE="${2:-default}"
CONTAINER_NAME="${3:-}"
OUTPUT_DIR="./k8s-memory-${POD_NAME}-$(date +%Y%m%d-%H%M%S)"
if [[ -z "${POD_NAME}" ]]; then
echo "Usage: $0 <pod-name> [namespace] [container-name]"
exit 1
fi
mkdir -p "${OUTPUT_DIR}"
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "${OUTPUT_DIR}/capture.log"
}
log "Capturing memory from pod ${NAMESPACE}/${POD_NAME}"
# Get pod information
kubectl get pod -n "${NAMESPACE}" "${POD_NAME}" -o yaml > "${OUTPUT_DIR}/pod-spec.yaml"
# Get node name
NODE=$(kubectl get pod -n "${NAMESPACE}" "${POD_NAME}" -o jsonpath='{.spec.nodeName}')
log "Pod is running on node: ${NODE}"
# If container not specified, get first container
if [[ -z "${CONTAINER_NAME}" ]]; then
CONTAINER_NAME=$(kubectl get pod -n "${NAMESPACE}" "${POD_NAME}" -o jsonpath='{.spec.containers[0].name}')
fi
log "Target container: ${CONTAINER_NAME}"
# Get container ID
CONTAINER_ID=$(kubectl get pod -n "${NAMESPACE}" "${POD_NAME}" -o jsonpath="{.status.containerStatuses[?(@.name=='${CONTAINER_NAME}')].containerID}" | sed 's/.*:\/\///')
log "Container ID: ${CONTAINER_ID}"
# Deploy forensics daemonset on the target node
log "Deploying forensics pod on node ${NODE}..."
cat > "${OUTPUT_DIR}/forensics-pod.yaml" << EOF
apiVersion: v1
kind: Pod
metadata:
name: forensics-${POD_NAME}-$(date +%s)
namespace: ${NAMESPACE}
labels:
app: forensics
target-pod: ${POD_NAME}
spec:
nodeName: ${NODE}
hostPID: true
hostNetwork: true
containers:
- name: forensics
image: nicolaka/netshoot:latest
command: ['sleep', 'infinity']
securityContext:
privileged: true
capabilities:
add:
- SYS_ADMIN
- SYS_PTRACE
volumeMounts:
- name: host-root
mountPath: /host
- name: container-runtime
mountPath: /run/containerd
- name: captures
mountPath: /captures
volumes:
- name: host-root
hostPath:
path: /
- name: container-runtime
hostPath:
path: /run/containerd
- name: captures
emptyDir: {}
restartPolicy: Never
EOF
FORENSICS_POD=$(kubectl apply -f "${OUTPUT_DIR}/forensics-pod.yaml" -o jsonpath='{.metadata.name}')
log "Forensics pod created: ${FORENSICS_POD}"
# Wait for pod to be ready
log "Waiting for forensics pod to be ready..."
kubectl wait --for=condition=Ready pod -n "${NAMESPACE}" "${FORENSICS_POD}" --timeout=60s
# Execute memory capture from forensics pod
log "Executing memory capture..."
# Get container PID
CONTAINER_PID=$(kubectl exec -n "${NAMESPACE}" "${FORENSICS_POD}" -- \
chroot /host crictl inspect "${CONTAINER_ID}" 2>/dev/null | jq -r '.info.pid' || \
kubectl exec -n "${NAMESPACE}" "${FORENSICS_POD}" -- \
chroot /host docker inspect --format '{{.State.Pid}}' "${CONTAINER_ID}" 2>/dev/null)
log "Container PID: ${CONTAINER_PID}"
# Capture memory using gcore
kubectl exec -n "${NAMESPACE}" "${FORENSICS_POD}" -- bash -c "
cd /captures
gcore -o core ${CONTAINER_PID}
" 2>&1 | tee -a "${OUTPUT_DIR}/capture.log"
# Copy memory dump from forensics pod
log "Retrieving memory dump..."
kubectl cp "${NAMESPACE}/${FORENSICS_POD}:/captures/" "${OUTPUT_DIR}/dumps/"
# Capture additional process information
kubectl exec -n "${NAMESPACE}" "${FORENSICS_POD}" -- bash -c "
cp /host/proc/${CONTAINER_PID}/maps /captures/maps.txt
cp /host/proc/${CONTAINER_PID}/status /captures/status.txt
cp /host/proc/${CONTAINER_PID}/cmdline /captures/cmdline.txt
cp /host/proc/${CONTAINER_PID}/environ /captures/environ.txt
ps -p ${CONTAINER_PID} -o pid,ppid,cmd,%mem,%cpu > /captures/process-info.txt
" 2>&1 | tee -a "${OUTPUT_DIR}/capture.log"
kubectl cp "${NAMESPACE}/${FORENSICS_POD}:/captures/" "${OUTPUT_DIR}/process-info/"
# Cleanup forensics pod
log "Cleaning up forensics pod..."
kubectl delete pod -n "${NAMESPACE}" "${FORENSICS_POD}"
# Calculate checksums
find "${OUTPUT_DIR}" -type f -exec sha256sum {} \; > "${OUTPUT_DIR}/checksums.txt"
log "Kubernetes pod memory capture complete!"
log "Output directory: ${OUTPUT_DIR}"
Memory Dump Analysis
Automated Memory Analysis Framework
Create a comprehensive memory analysis framework:
#!/usr/bin/env python3
# container-memory-analyzer.py - Container memory forensics analyzer
import os
import sys
import re
import subprocess
import json
from pathlib import Path
from typing import List, Dict, Set
import struct
class ContainerMemoryAnalyzer:
def __init__(self, memory_dump_path: str, output_dir: str):
self.memory_dump = Path(memory_dump_path)
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
self.findings = {
'credentials': [],
'network_artifacts': [],
'malware_indicators': [],
'suspicious_strings': [],
'crypto_keys': [],
'file_artifacts': []
}
def analyze(self):
"""Run all analysis modules"""
print(f"Analyzing memory dump: {self.memory_dump}")
if self.memory_dump.suffix == '.core' or 'core.' in self.memory_dump.name:
self.analyze_core_dump()
else:
self.analyze_raw_memory()
self.search_credentials()
self.search_network_artifacts()
self.search_crypto_material()
self.search_file_artifacts()
self.search_suspicious_patterns()
self.generate_report()
def analyze_core_dump(self):
"""Analyze ELF core dump using various tools"""
print("Analyzing ELF core dump...")
# Use GDB to extract information
gdb_script = self.output_dir / "gdb-script.txt"
with open(gdb_script, 'w') as f:
f.write("""
info proc mappings
info threads
info sharedlibrary
x/1000s $sp
quit
""")
try:
result = subprocess.run(
['gdb', '-batch', '-x', str(gdb_script), str(self.memory_dump)],
capture_output=True,
text=True,
timeout=300
)
with open(self.output_dir / "gdb-output.txt", 'w') as f:
f.write(result.stdout)
except Exception as e:
print(f"GDB analysis failed: {e}")
# Extract strings
self._extract_strings()
def analyze_raw_memory(self):
"""Analyze raw memory segments"""
print("Analyzing raw memory segments...")
if self.memory_dump.is_dir():
# Multiple memory segments
for mem_file in self.memory_dump.glob("*.bin"):
self._analyze_memory_segment(mem_file)
else:
# Single memory file
self._analyze_memory_segment(self.memory_dump)
def _analyze_memory_segment(self, mem_file: Path):
"""Analyze individual memory segment"""
print(f" Analyzing segment: {mem_file.name}")
# Extract strings
strings_file = self.output_dir / f"strings_{mem_file.stem}.txt"
try:
subprocess.run(
['strings', '-n', '6', str(mem_file)],
stdout=open(strings_file, 'w'),
timeout=60
)
except Exception as e:
print(f" String extraction failed: {e}")
def _extract_strings(self):
"""Extract strings from memory dump"""
print("Extracting strings...")
strings_file = self.output_dir / "all_strings.txt"
try:
subprocess.run(
['strings', '-a', '-n', '6', str(self.memory_dump)],
stdout=open(strings_file, 'w'),
timeout=300
)
except Exception as e:
print(f"String extraction failed: {e}")
def search_credentials(self):
"""Search for credentials in memory"""
print("Searching for credentials...")
patterns = {
'password': [
rb'password["\s:=]+([^\s"\']+)',
rb'PASSWORD["\s:=]+([^\s"\']+)',
rb'pwd["\s:=]+([^\s"\']+)',
],
'api_key': [
rb'api[_-]?key["\s:=]+([A-Za-z0-9_\-]+)',
rb'API[_-]?KEY["\s:=]+([A-Za-z0-9_\-]+)',
],
'token': [
rb'token["\s:=]+([A-Za-z0-9_\-\.]+)',
rb'TOKEN["\s:=]+([A-Za-z0-9_\-\.]+)',
rb'jwt["\s:=]+([A-Za-z0-9_\-\.]+)',
],
'private_key': [
rb'-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----.*?-----END (?:RSA |EC |DSA )?PRIVATE KEY-----',
],
'aws_key': [
rb'AKIA[0-9A-Z]{16}',
rb'aws[_-]?secret[_-]?access[_-]?key["\s:=]+([A-Za-z0-9/+=]+)',
],
'connection_string': [
rb'(?:mysql|postgres|mongodb|redis)://[^\s"\']+',
]
}
# Search in strings file
strings_files = list(self.output_dir.glob("strings_*.txt")) + \
list(self.output_dir.glob("all_strings.txt"))
for strings_file in strings_files:
if not strings_file.exists():
continue
with open(strings_file, 'rb') as f:
content = f.read()
for cred_type, pattern_list in patterns.items():
for pattern in pattern_list:
matches = re.finditer(pattern, content, re.IGNORECASE | re.DOTALL)
for match in matches:
self.findings['credentials'].append({
'type': cred_type,
'value': match.group(0).decode('utf-8', errors='ignore')[:200],
'source': strings_file.name
})
print(f" Found {len(self.findings['credentials'])} potential credentials")
def search_network_artifacts(self):
"""Search for network-related artifacts"""
print("Searching for network artifacts...")
patterns = {
'ip_address': rb'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b',
'domain': rb'(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}',
'url': rb'https?://[^\s"\'<>]+',
'email': rb'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
}
strings_files = list(self.output_dir.glob("strings_*.txt")) + \
list(self.output_dir.glob("all_strings.txt"))
unique_artifacts = {k: set() for k in patterns.keys()}
for strings_file in strings_files:
if not strings_file.exists():
continue
with open(strings_file, 'rb') as f:
content = f.read()
for artifact_type, pattern in patterns.items():
matches = re.finditer(pattern, content)
for match in matches:
artifact = match.group(0).decode('utf-8', errors='ignore')
unique_artifacts[artifact_type].add(artifact)
# Store findings
for artifact_type, artifacts in unique_artifacts.items():
for artifact in artifacts:
self.findings['network_artifacts'].append({
'type': artifact_type,
'value': artifact
})
print(f" Found {len(self.findings['network_artifacts'])} network artifacts")
def search_crypto_material(self):
"""Search for cryptographic keys and certificates"""
print("Searching for cryptographic material...")
patterns = {
'private_key': rb'-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----.*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----',
'certificate': rb'-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----',
'ssh_key': rb'ssh-(?:rsa|dss|ed25519) [A-Za-z0-9+/]+=*',
}
strings_files = list(self.output_dir.glob("strings_*.txt")) + \
list(self.output_dir.glob("all_strings.txt"))
for strings_file in strings_files:
if not strings_file.exists():
continue
with open(strings_file, 'rb') as f:
content = f.read()
for key_type, pattern in patterns.items():
matches = re.finditer(pattern, content, re.DOTALL)
for match in matches:
self.findings['crypto_keys'].append({
'type': key_type,
'value': match.group(0).decode('utf-8', errors='ignore')[:500],
'source': strings_file.name
})
print(f" Found {len(self.findings['crypto_keys'])} cryptographic artifacts")
def search_file_artifacts(self):
"""Search for file paths and names"""
print("Searching for file artifacts...")
patterns = [
rb'/(?:usr|etc|var|opt|home|root)/[^\s"\'<>]+',
rb'[A-Z]:\\[^\s"\'<>]+',
]
strings_files = list(self.output_dir.glob("strings_*.txt")) + \
list(self.output_dir.glob("all_strings.txt"))
unique_paths = set()
for strings_file in strings_files:
if not strings_file.exists():
continue
with open(strings_file, 'rb') as f:
content = f.read()
for pattern in patterns:
matches = re.finditer(pattern, content)
for match in matches:
path = match.group(0).decode('utf-8', errors='ignore')
unique_paths.add(path)
for path in unique_paths:
self.findings['file_artifacts'].append({
'type': 'path',
'value': path
})
print(f" Found {len(self.findings['file_artifacts'])} file artifacts")
def search_suspicious_patterns(self):
"""Search for suspicious patterns indicating malware or attacks"""
print("Searching for suspicious patterns...")
suspicious_patterns = {
'reverse_shell': [
rb'/bin/(?:ba)?sh -i',
rb'nc -[el]',
rb'python.*socket\.connect',
rb'perl.*socket',
],
'crypto_miner': [
rb'xmrig',
rb'minerd',
rb'cpuminer',
rb'stratum\+tcp://',
rb'cryptonight',
],
'persistence': [
rb'crontab -e',
rb'\.bashrc',
rb'\.profile',
rb'/etc/rc\.local',
rb'systemd.*service',
],
'privilege_escalation': [
rb'sudo su',
rb'chmod \+s',
rb'setuid',
rb'/etc/sudoers',
],
'data_exfiltration': [
rb'curl.*-d',
rb'wget.*--post',
rb'base64 -[de]',
rb'tar.*czf.*-\s*\|',
]
}
strings_files = list(self.output_dir.glob("strings_*.txt")) + \
list(self.output_dir.glob("all_strings.txt"))
for strings_file in strings_files:
if not strings_file.exists():
continue
with open(strings_file, 'rb') as f:
content = f.read()
for indicator_type, pattern_list in suspicious_patterns.items():
for pattern in pattern_list:
matches = re.finditer(pattern, content, re.IGNORECASE)
for match in matches:
self.findings['suspicious_strings'].append({
'type': indicator_type,
'value': match.group(0).decode('utf-8', errors='ignore')[:200],
'source': strings_file.name
})
print(f" Found {len(self.findings['suspicious_strings'])} suspicious patterns")
def generate_report(self):
"""Generate comprehensive forensics report"""
print("\nGenerating forensics report...")
report_file = self.output_dir / "forensics-report.txt"
with open(report_file, 'w') as f:
f.write("CONTAINER MEMORY FORENSICS REPORT\n")
f.write("=" * 80 + "\n\n")
f.write(f"Memory Dump: {self.memory_dump}\n")
f.write(f"Analysis Date: {subprocess.check_output(['date']).decode().strip()}\n")
f.write(f"Analyst: {os.environ.get('USER', 'Unknown')}\n\n")
# Credentials section
f.write("CREDENTIALS AND SECRETS\n")
f.write("-" * 80 + "\n")
if self.findings['credentials']:
cred_types = {}
for cred in self.findings['credentials']:
cred_type = cred['type']
if cred_type not in cred_types:
cred_types[cred_type] = []
cred_types[cred_type].append(cred)
for cred_type, creds in cred_types.items():
f.write(f"\n{cred_type.upper().replace('_', ' ')}: {len(creds)} found\n")
for i, cred in enumerate(creds[:10], 1): # Limit to 10 per type
f.write(f" [{i}] {cred['value']}\n")
if len(creds) > 10:
f.write(f" ... and {len(creds) - 10} more\n")
else:
f.write("No credentials found\n")
# Network artifacts
f.write("\n\nNETWORK ARTIFACTS\n")
f.write("-" * 80 + "\n")
if self.findings['network_artifacts']:
artifact_types = {}
for artifact in self.findings['network_artifacts']:
artifact_type = artifact['type']
if artifact_type not in artifact_types:
artifact_types[artifact_type] = set()
artifact_types[artifact_type].add(artifact['value'])
for artifact_type, artifacts in artifact_types.items():
f.write(f"\n{artifact_type.upper().replace('_', ' ')}: {len(artifacts)} unique\n")
for i, artifact in enumerate(sorted(artifacts)[:20], 1):
f.write(f" [{i}] {artifact}\n")
if len(artifacts) > 20:
f.write(f" ... and {len(artifacts) - 20} more\n")
else:
f.write("No network artifacts found\n")
# Cryptographic material
f.write("\n\nCRYPTOGRAPHIC MATERIAL\n")
f.write("-" * 80 + "\n")
if self.findings['crypto_keys']:
for i, key in enumerate(self.findings['crypto_keys'][:5], 1):
f.write(f"\n[{i}] {key['type'].upper()}:\n")
f.write(f"{key['value'][:200]}\n")
if len(self.findings['crypto_keys']) > 5:
f.write(f"\n... and {len(self.findings['crypto_keys']) - 5} more\n")
else:
f.write("No cryptographic material found\n")
# Suspicious patterns
f.write("\n\nSUSPICIOUS PATTERNS (POTENTIAL MALWARE/ATTACKS)\n")
f.write("-" * 80 + "\n")
if self.findings['suspicious_strings']:
pattern_types = {}
for pattern in self.findings['suspicious_strings']:
pattern_type = pattern['type']
if pattern_type not in pattern_types:
pattern_types[pattern_type] = []
pattern_types[pattern_type].append(pattern)
for pattern_type, patterns in pattern_types.items():
f.write(f"\n{pattern_type.upper().replace('_', ' ')}: {len(patterns)} indicators\n")
for i, pattern in enumerate(patterns[:10], 1):
f.write(f" [{i}] {pattern['value']}\n")
if len(patterns) > 10:
f.write(f" ... and {len(patterns) - 10} more\n")
else:
f.write("No suspicious patterns found\n")
# File artifacts
f.write("\n\nFILE ARTIFACTS\n")
f.write("-" * 80 + "\n")
if self.findings['file_artifacts']:
f.write(f"Total file paths found: {len(self.findings['file_artifacts'])}\n\n")
interesting_paths = [
p for p in self.findings['file_artifacts']
if any(x in p['value'].lower() for x in ['tmp', 'var', 'etc', 'root', 'home'])
]
f.write("Interesting paths:\n")
for i, path in enumerate(sorted(interesting_paths, key=lambda x: x['value'])[:50], 1):
f.write(f" [{i}] {path['value']}\n")
else:
f.write("No file artifacts found\n")
# Summary
f.write("\n\nSUMMARY\n")
f.write("-" * 80 + "\n")
f.write(f"Credentials found: {len(self.findings['credentials'])}\n")
f.write(f"Network artifacts: {len(self.findings['network_artifacts'])}\n")
f.write(f"Cryptographic keys: {len(self.findings['crypto_keys'])}\n")
f.write(f"Suspicious patterns: {len(self.findings['suspicious_strings'])}\n")
f.write(f"File artifacts: {len(self.findings['file_artifacts'])}\n")
# Save JSON report
json_file = self.output_dir / "forensics-report.json"
with open(json_file, 'w') as f:
json.dump(self.findings, f, indent=2)
print(f"Report generated: {report_file}")
print(f"JSON report: {json_file}")
def main():
if len(sys.argv) < 2:
print("Usage: container-memory-analyzer.py <memory-dump> [output-dir]")
sys.exit(1)
memory_dump = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) > 2 else "./memory-analysis"
analyzer = ContainerMemoryAnalyzer(memory_dump, output_dir)
analyzer.analyze()
if __name__ == "__main__":
main()
Conclusion
Container memory forensics is a critical capability for incident response teams investigating security incidents in containerized environments. By combining proper memory capture techniques with comprehensive analysis workflows, organizations can extract valuable forensic artifacts even from ephemeral container workloads.
Key takeaways:
- Capture Early: Container memory is volatile; capture as soon as an incident is detected
- Multiple Methods: Use multiple capture techniques to ensure comprehensive evidence collection
- Automate Analysis: Automated analysis tools can quickly identify key artifacts and indicators
- Preserve Context: Capture process information, network state, and filesystem alongside memory
- Handle with Care: Maintain chain of custody and evidence integrity throughout the process
The tools and techniques presented provide a foundation for enterprise-grade container memory forensics, enabling security teams to effectively investigate incidents and extract critical evidence from containerized workloads.