Files
2026-08-19 08:04:59 +02:00

123 lines
4.2 KiB
Python

"""Pure helpers that format captured authentication material for hashcat.
Supported hashcat modes:
5500 NetNTLMv1
5600 NetNTLMv2
18200 Kerberos AS-REP (etype 23)
13100 Kerberos TGS-REP (etype 23)
"""
from __future__ import annotations
import base64
from typing import Optional
def _hex(s: Optional[str]) -> str:
"""Normalize a hexadecimal string and remove separators."""
if not s:
return ""
return "".join(c for c in str(s).lower() if c in "0123456789abcdef")
# --------------------------------------------------------------------------- #
# NTLM / NetNTLM
# --------------------------------------------------------------------------- #
def build_netntlmv2(user, domain, server_challenge, nt_response) -> Optional[dict]:
"""NetNTLMv2 (hashcat -m 5600).
Format: user::domain:serverchallenge:NTproofstr:blob
The NT response contains a 16-byte proof followed by the remaining blob.
"""
chal = _hex(server_challenge)
nt = _hex(nt_response)
if not user or len(chal) != 16 or len(nt) < 32:
return None
ntproof, blob = nt[:32], nt[32:]
line = f"{user}::{domain or ''}:{chal}:{ntproof}:{blob}"
return {"hashcat": line, "hashcat_mode": 5600, "format": "NetNTLMv2"}
def build_netntlmv1(user, host, server_challenge, lm_response, nt_response) -> Optional[dict]:
"""NetNTLMv1 (hashcat -m 5500).
Format: user::host:LMresponse:NTresponse:serverchallenge
"""
chal = _hex(server_challenge)
nt = _hex(nt_response)
lm = _hex(lm_response) or nt
if not user or len(chal) != 16 or len(nt) < 32:
return None
line = f"{user}::{host or ''}:{lm}:{nt}:{chal}"
return {"hashcat": line, "hashcat_mode": 5500, "format": "NetNTLMv1"}
# --------------------------------------------------------------------------- #
# Kerberos
# --------------------------------------------------------------------------- #
def build_krb5_asrep(user, realm, etype, cipher) -> Optional[dict]:
"""Kerberos AS-REP Roasting (hashcat -m 18200, etype 23).
Format: $krb5asrep$<etype>$user@REALM:<checksum>$<edata>
cipher = checksum (16 Byte) + edata.
"""
ci = _hex(cipher)
if not user or len(ci) <= 32:
return None
et = str(etype or 23)
checksum, edata = ci[:32], ci[32:]
realm_u = (realm or "").upper()
line = f"$krb5asrep${et}${user}@{realm_u}:{checksum}${edata}"
mode = 18200 if et == "23" else None
return {"hashcat": line, "hashcat_mode": mode, "format": f"krb5asrep(etype={et})"}
def build_krb5_tgsrep(user, realm, spn, etype, cipher) -> Optional[dict]:
"""Kerberoasting / TGS-REP (hashcat -m 13100, etype 23).
Format: $krb5tgs$<etype>$*user$realm$spn*$<checksum>$<edata>
"""
ci = _hex(cipher)
if not user or len(ci) <= 32:
return None
et = str(etype or 23)
checksum, edata = ci[:32], ci[32:]
realm_u = (realm or "").upper()
line = f"$krb5tgs${et}$*{user}${realm_u}${spn or ''}*${checksum}${edata}"
mode = 13100 if et == "23" else None
return {"hashcat": line, "hashcat_mode": mode, "format": f"krb5tgs(etype={et})"}
# --------------------------------------------------------------------------- #
# SMTP AUTH is clear text with Base64 encoding only.
# --------------------------------------------------------------------------- #
def _b64(token: str) -> Optional[str]:
try:
return base64.b64decode(str(token).strip() + "===").decode("utf-8", "ignore")
except Exception:
return None
def decode_smtp_auth_plain(token: str) -> Optional[tuple[Optional[str], Optional[str]]]:
"""Decode an AUTH PLAIN token into username and password."""
raw = _b64(token)
if raw is None:
return None
parts = raw.split("\x00")
if len(parts) >= 3:
return parts[1] or None, parts[2] or None
if len(parts) == 2:
return parts[0] or None, parts[1] or None
return None
def decode_smtp_auth_login(user_token: Optional[str],
pass_token: Optional[str]) -> tuple[Optional[str], Optional[str]]:
"""Decode the separately encoded AUTH LOGIN username and password."""
user = _b64(user_token) if user_token else None
pw = _b64(pass_token) if pass_token else None
return user, pw