123 lines
4.2 KiB
Python
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
|