Files
Anthropic-Cybersecurity-Skills/skills/performing-hash-cracking-with-hashcat/scripts/agent.py
T
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

172 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""Agent for performing hash cracking with hashcat — hash identification, attack management, and result analysis."""
import json
import argparse
import subprocess
import hashlib
import re
from pathlib import Path
from datetime import datetime
HASH_PATTERNS = {
"MD5": (r"^[a-f0-9]{32}$", 0),
"SHA1": (r"^[a-f0-9]{40}$", 100),
"SHA256": (r"^[a-f0-9]{64}$", 1400),
"SHA512": (r"^[a-f0-9]{128}$", 1700),
"NTLM": (r"^[a-f0-9]{32}$", 1000),
"bcrypt": (r"^\$2[aby]?\$\d+\$.{53}$", 3200),
"sha512crypt": (r"^\$6\$[^\$]+\$[a-zA-Z0-9./]{86}$", 1800),
"sha256crypt": (r"^\$5\$[^\$]+\$[a-zA-Z0-9./]{43}$", 7400),
"md5crypt": (r"^\$1\$[^\$]+\$[a-zA-Z0-9./]{22}$", 500),
"NetNTLMv2": (r"^[^:]+::\S+:[a-f0-9]{16}:[a-f0-9]{32}:[a-f0-9]+$", 5600),
"Kerberos_TGS": (r"^\$krb5tgs\$", 13100),
"Kerberos_AS": (r"^\$krb5asrep\$", 18200),
}
def identify_hash(hash_string):
"""Identify hash type and return hashcat mode."""
candidates = []
for name, (pattern, mode) in HASH_PATTERNS.items():
if re.match(pattern, hash_string.strip(), re.IGNORECASE):
candidates.append({"type": name, "hashcat_mode": mode})
return {"hash": hash_string[:40] + "..." if len(hash_string) > 40 else hash_string, "candidates": candidates}
def identify_hashes_file(hash_file):
"""Identify hash types from a file of hashes."""
hashes = Path(hash_file).read_text(encoding="utf-8", errors="replace").strip().splitlines()
results = []
for h in hashes[:100]:
h = h.strip()
if h:
results.append(identify_hash(h))
types = {}
for r in results:
for c in r["candidates"]:
types[c["type"]] = types.get(c["type"], 0) + 1
return {"total_hashes": len(results), "type_distribution": types, "samples": results[:5]}
def run_hashcat(hash_file, mode, attack="dictionary", wordlist=None, rules=None, mask=None):
"""Execute hashcat with specified attack mode."""
cmd = ["hashcat", "-m", str(mode), "--quiet", "--potfile-disable", "-o", "/tmp/hashcat_out.txt"]
if attack == "dictionary":
if not wordlist:
return {"error": "wordlist required for dictionary attack"}
cmd += ["-a", "0", hash_file, wordlist]
if rules:
cmd += ["-r", rules]
elif attack == "brute":
m = mask or "?a?a?a?a?a?a?a?a"
cmd += ["-a", "3", hash_file, m]
elif attack == "combinator":
if not wordlist:
return {"error": "two wordlists required (comma-separated)"}
wl = wordlist.split(",")
if len(wl) != 2:
return {"error": "combinator needs two wordlists: list1.txt,list2.txt"}
cmd += ["-a", "1", hash_file, wl[0], wl[1]]
else:
return {"error": f"Unknown attack type: {attack}"}
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
cracked = []
out_file = Path("/tmp/hashcat_out.txt")
if out_file.exists():
for line in out_file.read_text().strip().splitlines():
parts = line.split(":", 1)
if len(parts) == 2:
cracked.append({"hash": parts[0][:30] + "...", "plain": parts[1]})
return {
"attack": attack, "mode": mode, "cracked_count": len(cracked),
"cracked": cracked[:50], "return_code": result.returncode,
"stderr_snippet": result.stderr[:300] if result.stderr else "",
}
except FileNotFoundError:
return {"error": "hashcat not found in PATH"}
except subprocess.TimeoutExpired:
return {"error": "hashcat timed out after 3600s"}
def parse_hashcat_status(potfile):
"""Parse hashcat potfile for cracked results."""
cracked = []
try:
for line in Path(potfile).read_text(encoding="utf-8", errors="replace").strip().splitlines():
parts = line.split(":", 1)
if len(parts) == 2:
cracked.append({"hash": parts[0], "plain": parts[1]})
except FileNotFoundError:
return {"error": f"Potfile not found: {potfile}"}
passwords = [c["plain"] for c in cracked]
length_dist = {}
for p in passwords:
l = len(p)
bucket = f"{l}" if l <= 8 else "9-12" if l <= 12 else "13+"
length_dist[bucket] = length_dist.get(bucket, 0) + 1
charset = {"lowercase_only": 0, "uppercase_mixed": 0, "with_digits": 0, "with_special": 0}
for p in passwords:
if re.match(r"^[a-z]+$", p):
charset["lowercase_only"] += 1
elif re.search(r"\d", p):
charset["with_digits"] += 1
elif re.search(r"[!@#$%^&*()_+=\-\[\]{};:'\",.<>?/\\|`~]", p):
charset["with_special"] += 1
else:
charset["uppercase_mixed"] += 1
return {
"total_cracked": len(cracked),
"length_distribution": length_dist,
"charset_analysis": charset,
"top_passwords": [p for p, _ in __import__("collections").Counter(passwords).most_common(10)],
}
def generate_hash(plaintext, algorithm="sha256"):
"""Generate hash of a plaintext string for testing."""
algos = {"md5": hashlib.md5, "sha1": hashlib.sha1, "sha256": hashlib.sha256, "sha512": hashlib.sha512}
if algorithm not in algos:
return {"error": f"Unsupported: {algorithm}. Use: {list(algos.keys())}"}
h = algos[algorithm](plaintext.encode()).hexdigest()
return {"plaintext": plaintext, "algorithm": algorithm, "hash": h}
def main():
parser = argparse.ArgumentParser(description="Hashcat Hash Cracking Agent")
sub = parser.add_subparsers(dest="command")
i = sub.add_parser("identify", help="Identify hash type")
i.add_argument("--hash", help="Single hash string")
i.add_argument("--file", help="File containing hashes")
c = sub.add_parser("crack", help="Run hashcat")
c.add_argument("--hash-file", required=True)
c.add_argument("--mode", type=int, required=True, help="Hashcat mode number")
c.add_argument("--attack", default="dictionary", choices=["dictionary", "brute", "combinator"])
c.add_argument("--wordlist", help="Wordlist path (or two comma-separated for combinator)")
c.add_argument("--rules", help="Hashcat rules file")
c.add_argument("--mask", help="Brute force mask")
p = sub.add_parser("parse", help="Parse potfile results")
p.add_argument("--potfile", required=True)
g = sub.add_parser("gen", help="Generate test hash")
g.add_argument("--text", required=True)
g.add_argument("--algo", default="sha256")
args = parser.parse_args()
if args.command == "identify":
result = identify_hashes_file(args.file) if args.file else identify_hash(args.hash) if args.hash else {"error": "provide --hash or --file"}
elif args.command == "crack":
result = run_hashcat(args.hash_file, args.mode, args.attack, args.wordlist, args.rules, args.mask)
elif args.command == "parse":
result = parse_hashcat_status(args.potfile)
elif args.command == "gen":
result = generate_hash(args.text, args.algo)
else:
parser.print_help()
return
print(json.dumps(result, indent=2, default=str))
if __name__ == "__main__":
main()