mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-30 21:29:40 +03:00
Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Anthropic Agent Skills Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,76 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```json
|
||||
{
|
||||
"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..."]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,241 @@
|
||||
#!/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
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Dict, 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()
|
||||
Reference in New Issue
Block a user