feat: add wave 1 plugins (dns, encode, hash, defang, revshell, cidr)
All pure stdlib, zero external dependencies: - dns: raw UDP resolver with A/AAAA/MX/NS/TXT/CNAME/PTR/SOA - encode: base64, hex, URL, ROT13 encode/decode - hash: md5/sha1/sha256/sha512 generation + type identification - defang: IOC defanging/refanging for safe sharing - revshell: reverse shell one-liners for 11 languages - cidr: subnet calculator with IP membership check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
75
plugins/hash.py
Normal file
75
plugins/hash.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""Plugin: hash strings and identify hash types."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
from derp.plugin import command
|
||||
|
||||
_ALGOS = ("md5", "sha1", "sha256", "sha512")
|
||||
|
||||
# Patterns for hash identification (length -> possible types)
|
||||
_HASH_PATTERNS: list[tuple[str, int, str]] = [
|
||||
(r"^[a-fA-F0-9]{32}$", 32, "MD5"),
|
||||
(r"^[a-fA-F0-9]{40}$", 40, "SHA-1"),
|
||||
(r"^[a-fA-F0-9]{56}$", 56, "SHA-224"),
|
||||
(r"^[a-fA-F0-9]{64}$", 64, "SHA-256 / SHA3-256"),
|
||||
(r"^[a-fA-F0-9]{96}$", 96, "SHA-384 / SHA3-384"),
|
||||
(r"^[a-fA-F0-9]{128}$", 128, "SHA-512 / SHA3-512"),
|
||||
(r"^\$2[aby]\$\d{2}\$.{53}$", 0, "bcrypt"),
|
||||
(r"^\$6\$", 0, "sha512crypt"),
|
||||
(r"^\$5\$", 0, "sha256crypt"),
|
||||
(r"^\$1\$", 0, "md5crypt"),
|
||||
(r"^[a-fA-F0-9]{16}$", 16, "MySQL 3.x / Half MD5"),
|
||||
(r"^\*[a-fA-F0-9]{40}$", 0, "MySQL 4.1+"),
|
||||
]
|
||||
|
||||
|
||||
@command("hash", help="Hash text: !hash [algo] <text>")
|
||||
async def cmd_hash(bot, message):
|
||||
"""Generate hash digests.
|
||||
|
||||
!hash hello -> MD5, SHA1, SHA256
|
||||
!hash sha512 hello -> specific algorithm
|
||||
"""
|
||||
parts = message.text.split(None, 2)
|
||||
if len(parts) < 2:
|
||||
await bot.reply(message, f"Usage: !hash [{'|'.join(_ALGOS)}] <text>")
|
||||
return
|
||||
|
||||
# Check if first arg is an algorithm name
|
||||
if len(parts) >= 3 and parts[1].lower() in _ALGOS:
|
||||
algo = parts[1].lower()
|
||||
text = parts[2]
|
||||
digest = hashlib.new(algo, text.encode()).hexdigest()
|
||||
await bot.reply(message, f"{algo}: {digest}")
|
||||
return
|
||||
|
||||
# No algorithm specified -- show all common hashes
|
||||
text = message.text.split(None, 1)[1]
|
||||
results = []
|
||||
for algo in ("md5", "sha1", "sha256"):
|
||||
digest = hashlib.new(algo, text.encode()).hexdigest()
|
||||
results.append(f"{algo}:{digest}")
|
||||
await bot.reply(message, " ".join(results))
|
||||
|
||||
|
||||
@command("hashid", help="Identify hash type: !hashid <hash>")
|
||||
async def cmd_hashid(bot, message):
|
||||
"""Identify a hash type by its format and length."""
|
||||
parts = message.text.split(None, 1)
|
||||
if len(parts) < 2:
|
||||
await bot.reply(message, "Usage: !hashid <hash>")
|
||||
return
|
||||
|
||||
value = parts[1].strip()
|
||||
matches = []
|
||||
for pattern, _, name in _HASH_PATTERNS:
|
||||
if re.match(pattern, value):
|
||||
matches.append(name)
|
||||
|
||||
if matches:
|
||||
await bot.reply(message, f"Possible: {', '.join(matches)}")
|
||||
else:
|
||||
await bot.reply(message, f"Unknown hash format (length: {len(value)})")
|
||||
Reference in New Issue
Block a user