mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-28 23:20:58 +03:00
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
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user