digital forensics

Extracting Credentials from Memory Dump

Extract cached credentials, password hashes, Kerberos tickets, and authentication tokens from memory dumps using Volatility and Mimikatz for forensic investigation.

credential-extractionforensicsincident-responsememory-forensicsmimikatzpassword-hashesvolatility
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • During incident response to determine what credentials an attacker had access to
  • When assessing the scope of credential compromise after a breach
  • For identifying accounts that need immediate password resets
  • When investigating lateral movement and pass-the-hash/pass-the-ticket attacks
  • For recovering encryption keys or authentication tokens from process memory

Prerequisites

  • Memory dump in raw, ELF, or crash dump format
  • Volatility 3 with Windows symbol tables
  • Mimikatz (for offline analysis of extracted LSASS dumps)
  • pypykatz (Python implementation of Mimikatz for Linux-based analysis)
  • Understanding of Windows authentication (NTLM, Kerberos, DPAPI)
  • Appropriate legal authorization for credential extraction

Workflow

Step 1: Prepare Tools and Verify Memory Dump

# Install analysis tools
pip install volatility3 pypykatz
 
# Verify memory dump integrity
sha256sum /cases/case-2024-001/memory/memory.raw
 
# Identify the OS version
vol -f /cases/case-2024-001/memory/memory.raw windows.info
 
# Verify LSASS process exists in memory
vol -f /cases/case-2024-001/memory/memory.raw windows.pslist | grep -i lsass
 
# Output:
# PID    PPID   ImageFileName   Offset(V)        Threads  Handles  SessionId
# 684    564    lsass.exe       0xffffe00123456   35       1234     0

Step 2: Extract Credential Hashes with Volatility

# Dump SAM database hashes from memory
vol -f /cases/case-2024-001/memory/memory.raw windows.hashdump \
   | tee /cases/case-2024-001/analysis/hashdump.txt
 
# Output format:
# User           RID    LM Hash                          NTLM Hash
# Administrator  500    aad3b435b51404eeaad3b435b51404ee  fc525c9683e8fe067095ba2ddc971889
# Guest          501    aad3b435b51404eeaad3b435b51404ee  31d6cfe0d16ae931b73c59d7e0c089c0
# DefaultAccount 503    aad3b435b51404eeaad3b435b51404ee  31d6cfe0d16ae931b73c59d7e0c089c0
# svcbackup      1001   aad3b435b51404eeaad3b435b51404ee  2b576acbe6bcfda7294d6bd18041b8fe
 
# Extract LSA secrets
vol -f /cases/case-2024-001/memory/memory.raw windows.lsadump \
   | tee /cases/case-2024-001/analysis/lsadump.txt
 
# Extract cached domain credentials
vol -f /cases/case-2024-001/memory/memory.raw windows.cachedump \
   | tee /cases/case-2024-001/analysis/cachedump.txt

Step 3: Dump LSASS Process Memory for Detailed Analysis

# Dump LSASS process memory (PID from Step 1)
vol -f /cases/case-2024-001/memory/memory.raw windows.memmap --pid 684 --dump \
   -o /cases/case-2024-001/analysis/lsass_dump/
 
# Alternative: Dump all files associated with LSASS
vol -f /cases/case-2024-001/memory/memory.raw windows.dumpfiles --pid 684 \
   -o /cases/case-2024-001/analysis/lsass_files/
 
# Use procdump plugin for cleaner process dump
vol -f /cases/case-2024-001/memory/memory.raw windows.dumpfiles \
   --pid 684 -o /cases/case-2024-001/analysis/
 
# Rename the dump file for pypykatz/mimikatz
mv /cases/case-2024-001/analysis/lsass_dump/pid.684.dmp \
   /cases/case-2024-001/analysis/lsass.dmp

Step 4: Extract Credentials with pypykatz

# Run pypykatz against the full memory dump
pypykatz lsa minidump /cases/case-2024-001/analysis/lsass.dmp \
   > /cases/case-2024-001/analysis/pypykatz_results.txt 2>&1
 
# Run pypykatz against the raw memory dump directly
pypykatz rekall /cases/case-2024-001/memory/memory.raw \
   > /cases/case-2024-001/analysis/pypykatz_full.txt 2>&1
 
# Parse pypykatz output for structured analysis
python3 << 'PYEOF'
import json
 
# pypykatz can also output JSON
import subprocess
result = subprocess.run(
    ['pypykatz', 'lsa', 'minidump', '/cases/case-2024-001/analysis/lsass.dmp', '-j'],
    capture_output=True, text=True
)
 
if result.stdout:
    data = json.loads(result.stdout)
 
    print("=== EXTRACTED CREDENTIALS ===\n")
 
    for session_key, session in data.get('logon_sessions', {}).items():
        username = session.get('username', 'Unknown')
        domain = session.get('domainname', '')
        logon_server = session.get('logon_server', '')
        logon_time = session.get('logon_time', '')
        sid = session.get('sid', '')
 
        if username and username != '(null)':
            print(f"Session: {domain}\\{username}")
            print(f"  SID: {sid}")
            print(f"  Logon Server: {logon_server}")
            print(f"  Logon Time: {logon_time}")
 
            # NTLM hashes
            msv = session.get('msv_creds', [])
            for cred in msv:
                nt = cred.get('NThash', '')
                lm = cred.get('LMHash', '')
                if nt:
                    print(f"  NTLM Hash: {nt}")
                if lm:
                    print(f"  LM Hash: {lm}")
 
            # Kerberos tickets
            kerb = session.get('kerberos_creds', [])
            for cred in kerb:
                password = cred.get('password', '')
                if password:
                    print(f"  Kerberos Password: {password}")
                tickets = cred.get('tickets', [])
                for ticket in tickets:
                    print(f"  Kerberos Ticket: {ticket.get('server', '')} (type: {ticket.get('enc_type', '')})")
 
            # WDigest (plaintext on older systems)
            wdigest = session.get('wdigest_creds', [])
            for cred in wdigest:
                pwd = cred.get('password', '')
                if pwd:
                    print(f"  WDigest Password: {pwd}")
 
            # DPAPI master keys
            dpapi = session.get('dpapi_creds', [])
            for cred in dpapi:
                mk = cred.get('masterkey', '')
                if mk:
                    print(f"  DPAPI Master Key: {mk[:40]}...")
 
            print()
PYEOF

Step 5: Extract Kerberos Tickets and Tokens

# Extract Kerberos tickets from memory
python3 << 'PYEOF'
import subprocess, json
 
result = subprocess.run(
    ['pypykatz', 'lsa', 'minidump', '/cases/case-2024-001/analysis/lsass.dmp', '-j', '-k', '/cases/case-2024-001/analysis/kerberos/'],
    capture_output=True, text=True
)
 
# pypykatz exports .kirbi files to the specified directory
import os
kirbi_dir = '/cases/case-2024-001/analysis/kerberos/'
if os.path.exists(kirbi_dir):
    for f in os.listdir(kirbi_dir):
        if f.endswith('.kirbi'):
            filepath = os.path.join(kirbi_dir, f)
            size = os.path.getsize(filepath)
            print(f"  Kerberos ticket: {f} ({size} bytes)")
PYEOF
 
# Search process memory for authentication tokens and API keys
vol -f /cases/case-2024-001/memory/memory.raw windows.strings --pid 684 | \
   grep -iE '(bearer |authorization:|api[_-]key|token=|password=|secret=)' \
   > /cases/case-2024-001/analysis/auth_strings.txt
 
# Search for cloud credentials in memory
vol -f /cases/case-2024-001/memory/memory.raw windows.strings | \
   grep -iE '(AKIA[A-Z0-9]{16}|ASIA[A-Z0-9]{16}|aws_secret_access_key)' \
   > /cases/case-2024-001/analysis/aws_credentials.txt
 
# Search for browser session tokens
vol -f /cases/case-2024-001/memory/memory.raw windows.strings | \
   grep -iE '(session_id=|PHPSESSID=|JSESSIONID=|_ga=|sid=)' \
   > /cases/case-2024-001/analysis/session_tokens.txt

Step 6: Compile Credential Findings Report

# Generate credential compromise assessment
python3 << 'PYEOF'
print("""
CREDENTIAL EXTRACTION REPORT
==============================
Case: 2024-001
Source: memory.raw (16 GB Windows 10 memory dump)
Analysis Date: 2024-01-20
 
COMPROMISED ACCOUNTS:
=====================
 
1. Local Accounts (SAM):
   - Administrator (RID 500): NTLM hash extracted
   - svcbackup (RID 1001): NTLM hash extracted
   - SQLService (RID 1002): NTLM hash extracted
 
2. Domain Accounts (LSASS):
   - CORP\\admin.user: NTLM hash + Kerberos TGT
   - CORP\\svc.backup: NTLM hash + plaintext password (WDigest)
   - CORP\\domain.admin: Kerberos TGS tickets for 3 services
 
3. Cached Domain Credentials:
   - CORP\\helpdesk.user: DCC2 hash
   - CORP\\it.manager: DCC2 hash
 
4. Cloud Credentials:
   - AWS Access Key: AKIA... found in process memory (PID 3456)
   - Azure AD token found in browser process memory
 
IMMEDIATE ACTIONS REQUIRED:
- Reset passwords for all listed accounts
- Revoke and rotate AWS access keys
- Invalidate all active Kerberos tickets (krbtgt reset)
- Review DPAPI-protected data for additional exposure
""")
PYEOF

Key Concepts

Concept Description
LSASS (Local Security Authority) Windows process managing authentication, storing credentials in memory
NTLM hash NT LAN Manager hash of user password used for authentication
Kerberos TGT Ticket Granting Ticket allowing request of service tickets
WDigest Legacy authentication protocol storing plaintext passwords in memory (pre-Win8.1)
DPAPI Data Protection API using master keys derived from user credentials
DCC2 (Domain Cached Credentials) Cached domain password hashes for offline logon
LSA Secrets Encrypted service account passwords and other secrets stored by LSA
Pass-the-Hash Attack technique using extracted NTLM hashes without knowing the plaintext password

Tools & Systems

Tool Purpose
Volatility 3 Memory forensics framework with hashdump, lsadump, cachedump plugins
pypykatz Python implementation of Mimikatz for cross-platform LSASS analysis
Mimikatz Windows credential extraction tool (used offline against dumps)
secretsdump.py Impacket tool for extracting secrets from SAM/SYSTEM/SECURITY
hashcat Password hash cracking for recovered NTLM and DCC2 hashes
John the Ripper Alternative password cracking tool
Rubeus Kerberos ticket manipulation and extraction tool
Impacket Python toolkit for working with Windows network protocols and credentials

Common Scenarios

Scenario 1: Post-Breach Credential Assessment Extract all cached credentials from LSASS memory to determine which accounts were exposed, prioritize password resets based on privilege level, check for golden ticket material (krbtgt hash), assess if cloud credentials were accessible.

Scenario 2: Lateral Movement Investigation Extract NTLM hashes and Kerberos tickets to understand how the attacker moved between systems, identify pass-the-hash/pass-the-ticket artifacts, correlate extracted credentials with network logon events in event logs.

Scenario 3: Ransomware Operator Credential Theft Analyze pre-encryption memory dump for Mimikatz execution evidence, extract all available credential types, determine if domain admin credentials were obtained, assess if krbtgt was compromised (golden ticket), plan credential rotation strategy.

Scenario 4: Cloud Credential Theft from Endpoint Search endpoint memory for AWS access keys, Azure tokens, and GCP service account keys stored by CLI tools and browsers, identify exposed cloud permissions, immediately rotate discovered credentials, audit cloud audit logs for unauthorized access.

Output Format

Credential Extraction Summary:
  Source: memory.raw (16 GB, Windows 10 Build 19041)
  LSASS PID: 684
 
  Credentials Recovered:
    Local NTLM Hashes:        4 accounts
    Domain NTLM Hashes:       3 accounts
    Kerberos TGTs:             2 tickets
    Kerberos TGS:              5 service tickets
    Plaintext Passwords:       1 (WDigest - svc.backup)
    Cached Domain Creds:       2 DCC2 hashes
    LSA Secrets:               3 service account passwords
    DPAPI Master Keys:         4 keys recovered
    Cloud Credentials:         1 AWS access key, 1 Azure token
 
  Highest Privilege Compromised: Domain Admin (CORP\domain.admin)
 
  Recommended Actions:
    - Immediate: Reset all extracted account passwords
    - Immediate: Rotate AWS access key AKIA...
    - Urgent: Double krbtgt password reset (golden ticket mitigation)
    - High: Revoke all Kerberos tickets via krbtgt rotation
    - Medium: Audit DPAPI-protected data exposure
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md2.6 KB

API Reference: Memory Dump Credential Extraction Agent

Dependencies

Library Version Purpose
volatility3 >=2.0 Memory forensics framework (invoked via subprocess)
pypykatz >=0.6 Python Mimikatz for LSASS credential extraction

CLI Usage

python scripts/agent.py \
  --dump /cases/case-001/memory.raw \
  --output-dir /cases/case-001/analysis/ \
  --output credential_report.json

Functions

verify_dump(dump_path) -> dict

Checks file existence, computes size and SHA-256 of first 1MB for integrity.

run_vol3(dump_path, plugin, extra_args) -> str

Executes a volatility3 plugin via subprocess with 5-minute timeout. Returns stdout.

get_os_info(dump_path) -> dict

Runs windows.info to identify OS version and build from the memory image.

find_lsass_pid(dump_path) -> int

Runs windows.pslist and locates the LSASS process PID.

extract_hashdump(dump_path) -> list

Runs windows.hashdump to extract SAM database NTLM hashes for local accounts.

extract_lsadump(dump_path) -> list

Runs windows.lsadump to extract LSA secrets (service account passwords).

extract_cachedump(dump_path) -> list

Runs windows.cachedump to extract DCC2 cached domain credential hashes.

run_pypykatz(dump_path, output_dir) -> dict

Invokes pypykatz in JSON mode against LSASS minidump or full memory image.

parse_pypykatz_creds(pypykatz_data) -> list

Parses pypykatz JSON output into structured credential list with NTLM, Kerberos, WDigest, DPAPI.

search_cloud_keys(dump_path) -> list

Uses windows.strings to find AWS keys, JWT tokens, and auth strings in memory.

generate_report(dump_path, output_dir) -> dict

Orchestrates all extraction steps and compiles the final report with summary and actions.

Volatility3 Plugins Used

Plugin Purpose
windows.info OS identification
windows.pslist Process listing (find LSASS PID)
windows.hashdump SAM hash extraction
windows.lsadump LSA secret extraction
windows.cachedump Cached domain credential extraction
windows.strings String search for cloud keys and tokens

Output Schema

{
  "source": "/cases/memory.raw",
  "sam_hashes": [{"user": "Administrator", "rid": 500, "ntlm_hash": "fc52..."}],
  "lsass_creds": [{"user": "CORP\\admin", "cred_types": [{"type": "NTLM", "hash": "..."}]}],
  "cloud_keys": [{"type": "AWS Access Key", "value": "AKIA..."}],
  "summary": {"sam_hashes": 4, "lsass_creds": 3, "cloud_keys": 1},
  "actions": ["Reset passwords for all local accounts..."]
}

Scripts 1

agent.py9.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Memory dump credential extraction agent using volatility3 subprocess and pypykatz."""

import argparse
import hashlib
import json
import logging
import os
import re
import subprocess
from datetime import datetime
from typing import List, Optional

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

CLOUD_KEY_PATTERNS = [
    (r"AKIA[A-Z0-9]{16}", "AWS Access Key"),
    (r"ASIA[A-Z0-9]{16}", "AWS Temp Key"),
    (r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+", "JWT/Azure Token"),
]

AUTH_STRING_PATTERNS = [
    r"(?i)bearer\s+[A-Za-z0-9_\-\.]+",
    r"(?i)authorization:\s*\S+",
    r"(?i)api[_-]key[=:]\s*\S+",
    r"(?i)password[=:]\s*\S+",
]


def verify_dump(dump_path: str) -> dict:
    """Verify memory dump exists and compute hash."""
    if not os.path.isfile(dump_path):
        logger.error("Memory dump not found: %s", dump_path)
        return {"valid": False}
    size = os.path.getsize(dump_path)
    with open(dump_path, "rb") as f:
        sha256 = hashlib.sha256(f.read(1024 * 1024)).hexdigest()
    return {"valid": True, "size_bytes": size, "sha256_1mb": sha256}


def run_vol3(dump_path: str, plugin: str, extra_args: Optional[List[str]] = None) -> str:
    """Run a volatility3 plugin and return stdout."""
    cmd = ["vol", "-f", dump_path, plugin]
    if extra_args:
        cmd.extend(extra_args)
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        if result.returncode != 0 and result.stderr:
            logger.warning("vol3 %s stderr: %s", plugin, result.stderr[:200])
        return result.stdout
    except FileNotFoundError:
        logger.error("volatility3 (vol) not found in PATH")
        return ""
    except subprocess.TimeoutExpired:
        logger.error("vol3 %s timed out", plugin)
        return ""


def get_os_info(dump_path: str) -> dict:
    """Identify OS version from memory dump."""
    output = run_vol3(dump_path, "windows.info")
    info = {}
    for line in output.splitlines():
        if "\t" in line:
            parts = line.split("\t", 1)
            if len(parts) == 2:
                info[parts[0].strip()] = parts[1].strip()
    return info


def find_lsass_pid(dump_path: str) -> Optional[int]:
    """Find LSASS process PID from process list."""
    output = run_vol3(dump_path, "windows.pslist")
    for line in output.splitlines():
        if "lsass.exe" in line.lower():
            parts = line.split()
            for p in parts:
                if p.isdigit():
                    return int(p)
    return None


def extract_hashdump(dump_path: str) -> List[dict]:
    """Extract SAM hashes using windows.hashdump."""
    output = run_vol3(dump_path, "windows.hashdump")
    results = []
    for line in output.splitlines():
        parts = line.split()
        if len(parts) >= 4 and parts[1].isdigit():
            results.append({
                "user": parts[0], "rid": int(parts[1]),
                "lm_hash": parts[2], "ntlm_hash": parts[3],
            })
    logger.info("Extracted %d SAM hashes", len(results))
    return results


def extract_lsadump(dump_path: str) -> List[dict]:
    """Extract LSA secrets using windows.lsadump."""
    output = run_vol3(dump_path, "windows.lsadump")
    results = []
    for line in output.splitlines():
        line = line.strip()
        if line and not line.startswith("Offset") and not line.startswith("-"):
            results.append({"raw": line})
    logger.info("Extracted %d LSA secret entries", len(results))
    return results


def extract_cachedump(dump_path: str) -> List[dict]:
    """Extract cached domain credentials using windows.cachedump."""
    output = run_vol3(dump_path, "windows.cachedump")
    results = []
    for line in output.splitlines():
        parts = line.split()
        if len(parts) >= 3 and parts[0] not in ("User", "---"):
            results.append({"user": parts[0], "domain": parts[1], "dcc2_hash": parts[2] if len(parts) > 2 else ""})
    logger.info("Extracted %d cached domain credentials", len(results))
    return results


def run_pypykatz(dump_path: str, output_dir: str) -> dict:
    """Run pypykatz against LSASS minidump or full memory for credential extraction."""
    lsass_dmp = os.path.join(output_dir, "lsass.dmp")
    target = lsass_dmp if os.path.isfile(lsass_dmp) else dump_path
    mode = "minidump" if target == lsass_dmp else "rekall"
    cmd = ["pypykatz", "lsa", mode, target, "--json"]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
        if result.stdout:
            return json.loads(result.stdout)
    except FileNotFoundError:
        logger.warning("pypykatz not found; skipping LSASS credential extraction")
    except (json.JSONDecodeError, subprocess.TimeoutExpired) as exc:
        logger.warning("pypykatz error: %s", exc)
    return {}


def parse_pypykatz_creds(pypykatz_data: dict) -> List[dict]:
    """Parse pypykatz JSON output into structured credential list."""
    creds = []
    for session_key, session in pypykatz_data.get("logon_sessions", {}).items():
        username = session.get("username", "")
        domain = session.get("domainname", "")
        if not username or username == "(null)":
            continue
        entry = {"user": f"{domain}\\{username}", "sid": session.get("sid", ""),
                 "logon_server": session.get("logon_server", ""),
                 "logon_time": session.get("logon_time", ""), "cred_types": []}
        for msv in session.get("msv_creds", []):
            if msv.get("NThash"):
                entry["cred_types"].append({"type": "NTLM", "hash": msv["NThash"]})
        for kerb in session.get("kerberos_creds", []):
            if kerb.get("password"):
                entry["cred_types"].append({"type": "Kerberos_password", "value": kerb["password"]})
            for ticket in kerb.get("tickets", []):
                entry["cred_types"].append({"type": "Kerberos_ticket",
                                            "server": ticket.get("server", ""), "enc_type": ticket.get("enc_type", "")})
        for wd in session.get("wdigest_creds", []):
            if wd.get("password"):
                entry["cred_types"].append({"type": "WDigest", "value": wd["password"]})
        for dpapi in session.get("dpapi_creds", []):
            if dpapi.get("masterkey"):
                entry["cred_types"].append({"type": "DPAPI_masterkey", "key": dpapi["masterkey"][:40]})
        if entry["cred_types"]:
            creds.append(entry)
    return creds


def search_cloud_keys(dump_path: str) -> List[dict]:
    """Search memory strings for cloud credentials and auth tokens."""
    output = run_vol3(dump_path, "windows.strings", ["--pid", "0"])
    findings = []
    for pattern, label in CLOUD_KEY_PATTERNS:
        for match in re.findall(pattern, output):
            findings.append({"type": label, "value": match[:30] + "..."})
    for pattern in AUTH_STRING_PATTERNS:
        for match in re.findall(pattern, output):
            findings.append({"type": "auth_string", "value": match[:60]})
    logger.info("Found %d cloud/auth credential fragments", len(findings))
    return findings[:50]


def generate_report(dump_path: str, output_dir: str) -> dict:
    """Generate full credential extraction report."""
    os.makedirs(output_dir, exist_ok=True)
    report = {"analysis_date": datetime.utcnow().isoformat(), "source": dump_path}

    report["dump_info"] = verify_dump(dump_path)
    if not report["dump_info"].get("valid"):
        return report

    report["os_info"] = get_os_info(dump_path)
    report["lsass_pid"] = find_lsass_pid(dump_path)
    report["sam_hashes"] = extract_hashdump(dump_path)
    report["lsa_secrets"] = extract_lsadump(dump_path)
    report["cached_creds"] = extract_cachedump(dump_path)

    pypykatz_data = run_pypykatz(dump_path, output_dir)
    report["lsass_creds"] = parse_pypykatz_creds(pypykatz_data)
    report["cloud_keys"] = search_cloud_keys(dump_path)

    summary = {
        "sam_hashes": len(report["sam_hashes"]),
        "lsa_secrets": len(report["lsa_secrets"]),
        "cached_creds": len(report["cached_creds"]),
        "lsass_creds": len(report["lsass_creds"]),
        "cloud_keys": len(report["cloud_keys"]),
    }
    report["summary"] = summary
    report["actions"] = []
    if summary["sam_hashes"] > 0:
        report["actions"].append("Reset passwords for all local accounts with extracted NTLM hashes")
    if summary["lsass_creds"] > 0:
        report["actions"].append("Reset domain account passwords and perform double krbtgt rotation")
    if summary["cloud_keys"] > 0:
        report["actions"].append("Rotate all discovered cloud access keys and revoke active sessions")

    logger.info("Report complete: %s", json.dumps(summary))
    return report


def main():
    parser = argparse.ArgumentParser(description="Memory Dump Credential Extraction Agent")
    parser.add_argument("--dump", required=True, help="Path to memory dump file")
    parser.add_argument("--output-dir", default=".", help="Output directory")
    parser.add_argument("--output", default="credential_report.json")
    args = parser.parse_args()

    report = generate_report(args.dump, args.output_dir)
    out_path = os.path.join(args.output_dir, args.output)
    with open(out_path, "w") as f:
        json.dump(report, f, indent=2, default=str)
    logger.info("Report saved to %s", out_path)
    print(json.dumps(report, indent=2, default=str))


if __name__ == "__main__":
    main()
Keep exploring