From 0d060ca9cda2379a507081a8d483494cbca6f088 Mon Sep 17 00:00:00 2001 From: StrongWind1 <5987034+StrongWind1@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:41:03 -0400 Subject: [PATCH] chore: normalize file permissions and replace non-ASCII characters File permissions (22 files): - Set 644 on all source files that were incorrectly marked 755 (executable bit) across dementor/, docs/, tests/, and root configs. - Affected: __init__.py, filters.py, paths.py, servers.py, loader.py, protocols/{__init__,ftp,kerberos,ldap,llmnr,mdns,netbios,ntlm, quic,smtp,spnego}.py, tui/{completer,commands/{config,env,ipconfig}}.py, assets/Dementor.toml, docs/source/tui.rst, requirements.txt Non-ASCII replacements in dementor/ and root files (13 files): - Em-dash (U+2014) -> ' --' in comments and docstrings - En-dash (U+2013) -> '-' - Right arrow (U+2192) -> '->' - Curly/smart quotes (U+201C/U+201D) -> straight quotes - Section sign (U+00A7) -> 'S' for spec references - Tab (0x09) -> spaces in mysql.py - Fix 'donwngrade' typo (U+0175) in smtp.py - Affected: Dementor.toml, config/{toml,util}.py, db/connector.py, loader.py, protocols/{ftp,ntlm,smtp,ssdp}.py, tui/{completer,commands/{config,database}}.py, pyproject.toml, tests/test_db.py Non-ASCII replacements in docs/ (14 .rst files): - Em-dash (U+2014) -> '--' - En-dash (U+2013) -> '-' - Right single quote (U+2019) -> straight apostrophe - Intentionally kept: section sign, arrows, gear emoji, double vertical line, dagger, pointer arrows, greater-equal sign (these render well in Sphinx HTML output) Deleted: - tests/.gitkeep (unnecessary, directory contains .py test files) --- dementor/__init__.py | 0 dementor/assets/Dementor.toml | 40 +++++------ dementor/config/toml.py | 6 +- dementor/config/util.py | 12 ++-- dementor/db/connector.py | 8 +-- dementor/filters.py | 0 dementor/loader.py | 2 +- dementor/paths.py | 0 dementor/protocols/__init__.py | 0 dementor/protocols/ftp.py | 4 +- dementor/protocols/kerberos.py | 0 dementor/protocols/ldap.py | 0 dementor/protocols/llmnr.py | 0 dementor/protocols/mdns.py | 0 dementor/protocols/mysql.py | 2 +- dementor/protocols/netbios.py | 0 dementor/protocols/ntlm.py | 94 ++++++++++++------------- dementor/protocols/quic.py | 0 dementor/protocols/smtp.py | 2 +- dementor/protocols/spnego.py | 0 dementor/protocols/ssdp.py | 6 +- dementor/servers.py | 0 dementor/tui/commands/config.py | 6 +- dementor/tui/commands/database.py | 2 +- dementor/tui/commands/env.py | 0 dementor/tui/commands/ipconfig.py | 0 dementor/tui/completer.py | 2 +- docs/source/compat.rst | 2 +- docs/source/config/database.rst | 18 ++--- docs/source/config/dcerpc.rst | 4 +- docs/source/config/globals.rst | 4 +- docs/source/config/http.rst | 2 +- docs/source/config/imap.rst | 8 +-- docs/source/config/index.rst | 2 +- docs/source/config/ldap.rst | 2 +- docs/source/config/ntlm.rst | 40 +++++------ docs/source/config/pop3.rst | 2 +- docs/source/config/smb.rst | 12 ++-- docs/source/examples/kdc.rst | 6 +- docs/source/examples/smtp_downgrade.rst | 2 +- docs/source/examples/x11_cookies.rst | 4 +- docs/source/tui.rst | 0 pyproject.toml | 70 +++++++++--------- requirements.txt | 0 tests/.gitkeep | 0 tests/test_db.py | 30 ++++---- 46 files changed, 197 insertions(+), 197 deletions(-) mode change 100755 => 100644 dementor/__init__.py mode change 100755 => 100644 dementor/assets/Dementor.toml mode change 100755 => 100644 dementor/filters.py mode change 100755 => 100644 dementor/loader.py mode change 100755 => 100644 dementor/paths.py mode change 100755 => 100644 dementor/protocols/__init__.py mode change 100755 => 100644 dementor/protocols/ftp.py mode change 100755 => 100644 dementor/protocols/kerberos.py mode change 100755 => 100644 dementor/protocols/ldap.py mode change 100755 => 100644 dementor/protocols/llmnr.py mode change 100755 => 100644 dementor/protocols/mdns.py mode change 100755 => 100644 dementor/protocols/netbios.py mode change 100755 => 100644 dementor/protocols/ntlm.py mode change 100755 => 100644 dementor/protocols/quic.py mode change 100755 => 100644 dementor/protocols/smtp.py mode change 100755 => 100644 dementor/protocols/spnego.py mode change 100755 => 100644 dementor/servers.py mode change 100755 => 100644 dementor/tui/commands/config.py mode change 100755 => 100644 dementor/tui/commands/env.py mode change 100755 => 100644 dementor/tui/commands/ipconfig.py mode change 100755 => 100644 dementor/tui/completer.py mode change 100755 => 100644 docs/source/tui.rst mode change 100755 => 100644 requirements.txt delete mode 100644 tests/.gitkeep diff --git a/dementor/__init__.py b/dementor/__init__.py old mode 100755 new mode 100644 diff --git a/dementor/assets/Dementor.toml b/dementor/assets/Dementor.toml old mode 100755 new mode 100644 index 190a309..e67c587 --- a/dementor/assets/Dementor.toml +++ b/dementor/assets/Dementor.toml @@ -16,8 +16,8 @@ # # The three NTLM settings (Challenge, DisableExtendedSessionSecurity, # DisableNTLMv2) additionally fall back through two more levels: -# 3. [NTLM] section — shared default for all NTLM-enabled protocols -# 4. [Globals] section — last resort +# 3. [NTLM] section -- shared default for all NTLM-enabled protocols +# 4. [Globals] section -- last resort # # All other settings stop at step 2. # @@ -294,7 +294,7 @@ SMB2Support = true ErrorCode = "STATUS_SMB_BAD_UID" # NTLM settings: Challenge, DisableExtendedSessionSecurity, DisableNTLMv2 -# Not set here → falls back to [NTLM]. Set here to override [NTLM] for all +# Not set here -> falls back to [NTLM]. Set here to override [NTLM] for all # SMB servers, or inside [[SMB.Server]] to override for a single server only. # Challenge = "1337LEET" @@ -308,7 +308,7 @@ Port = 139 [[SMB.Server]] Port = 445 -# Per-server overrides (highest priority — override [SMB] and [NTLM] for this port only): +# Per-server overrides (highest priority -- override [SMB] and [NTLM] for this port only): # FQDN = "other.corp.com" # ServerOS = "Windows Server 2022" # ErrorCode = "STATUS_ACCESS_DENIED" @@ -350,7 +350,7 @@ Downgrade = true RequireSTARTTLS = false # NTLM settings: Challenge, DisableExtendedSessionSecurity, DisableNTLMv2 -# Not set here → falls back to [NTLM]. Set here to override [NTLM] for all +# Not set here -> falls back to [NTLM]. Set here to override [NTLM] for all # SMTP servers, or inside [[SMTP.Server]] to override for a single server only. # Challenge = "1337LEET" @@ -379,11 +379,11 @@ Port = 25 # SMB, HTTP, SMTP, LDAP, RPC, MSSQL, POP3, and IMAP. # # Resolution order for these three settings (highest priority first): -# 1. [[Protocol.Server]] entry — per-server override (list-based protocols only: +# 1. [[Protocol.Server]] entry -- per-server override (list-based protocols only: # SMB, HTTP, SMTP, LDAP, POP3, IMAP) -# 2. [Protocol] section — per-protocol override (e.g. [SMB], [HTTP]) -# 3. [NTLM] section — this section; the shared default -# 4. [Globals] section — broadest fallback +# 2. [Protocol] section -- per-protocol override (e.g. [SMB], [HTTP]) +# 3. [NTLM] section -- this section; the shared default +# 4. [Globals] section -- broadest fallback # # If a protocol section does not define Challenge, DisableExtendedSessionSecurity, # or DisableNTLMv2, it inherits the values set here. This lets you configure one @@ -399,8 +399,8 @@ Port = 25 # Accepted formats: # "hex:1122334455667788" explicit hex (preferred, unambiguous) # "ascii:1337LEET" explicit ASCII (preferred) -# "1122334455667788" 16 hex characters — auto-detected as hex -# "1337LEET" 8 ASCII characters — auto-detected as ASCII +# "1122334455667788" 16 hex characters -- auto-detected as hex +# "1337LEET" 8 ASCII characters -- auto-detected as ASCII # omitted / not set cryptographically random 8 bytes per run # # A fixed Challenge combined with DisableExtendedSessionSecurity = true makes @@ -423,9 +423,9 @@ DisableExtendedSessionSecurity = false # When true, TargetInfoFields are omitted from the CHALLENGE_MESSAGE. # Without TargetInfoFields clients cannot construct the NTLMv2 Blob -# (MS-NLMP §3.3.2), which has the following effect by client security level: -# Level 0–2 (older Windows, manually downgraded): fall back to NTLMv1. -# Level 3+ (all modern Windows defaults): refuse to authenticate — zero +# (MS-NLMP S3.3.2), which has the following effect by client security level: +# Level 0-2 (older Windows, manually downgraded): fall back to NTLMv1. +# Level 3+ (all modern Windows defaults): refuse to authenticate -- zero # hashes captured from these clients. # # Leave false unless specifically targeting legacy NTLMv1-only environments. @@ -497,7 +497,7 @@ TLS = false # ErrorCode = "unwillingToPerform" # NTLM settings: Challenge, DisableExtendedSessionSecurity, DisableNTLMv2 -# Not set here → falls back to [NTLM]. Set here to override [NTLM] for all +# Not set here -> falls back to [NTLM]. Set here to override [NTLM] for all # LDAP servers, or inside [[LDAP.Server]] to override for a single server only. # Challenge = "1337LEET" @@ -599,7 +599,7 @@ AuthSchemes = ["Basic", "Negotiate", "NTLM", "Bearer"] WebDAV = true # NTLM settings: Challenge, DisableExtendedSessionSecurity, DisableNTLMv2 -# Not set here → falls back to [NTLM]. Set here to override [NTLM] for all +# Not set here -> falls back to [NTLM]. Set here to override [NTLM] for all # HTTP servers, or inside [[HTTP.Server]] to override for a single server only. # Challenge = "1337LEET" @@ -651,7 +651,7 @@ Port = 80 [RPC] # NTLM settings: Challenge, DisableExtendedSessionSecurity, DisableNTLMv2 -# Not set here → falls back to [NTLM]. RPC uses a single server instance so +# Not set here -> falls back to [NTLM]. RPC uses a single server instance so # there is no per-server item level; only [RPC] or [NTLM] apply. # Challenge = "1337LEET" @@ -699,7 +699,7 @@ TargetPort = 49000 [MSSQL] # NTLM settings: Challenge, DisableExtendedSessionSecurity, DisableNTLMv2 -# Not set here → falls back to [NTLM]. MSSQL uses a single server instance so +# Not set here -> falls back to [NTLM]. MSSQL uses a single server instance so # there is no per-server item level; only [MSSQL] or [NTLM] apply. # Challenge = "1337LEET" @@ -762,7 +762,7 @@ InstanceName = "MSSQLServer" [POP3] # NTLM settings: Challenge, DisableExtendedSessionSecurity, DisableNTLMv2 -# Not set here → falls back to [NTLM]. Set here to override [NTLM] for all +# Not set here -> falls back to [NTLM]. Set here to override [NTLM] for all # POP3 servers, or inside [[POP3.Server]] to override for a single server only. # Challenge = "1337LEET" @@ -807,7 +807,7 @@ Port = 110 [IMAP] # NTLM settings: Challenge, DisableExtendedSessionSecurity, DisableNTLMv2 -# Not set here → falls back to [NTLM]. Set here to override [NTLM] for all +# Not set here -> falls back to [NTLM]. Set here to override [NTLM] for all # IMAP servers, or inside [[IMAP.Server]] to override for a single server only. # Challenge = "1337LEET" diff --git a/dementor/config/toml.py b/dementor/config/toml.py index bb34703..bfe6c75 100644 --- a/dementor/config/toml.py +++ b/dementor/config/toml.py @@ -27,7 +27,7 @@ _T = TypeVar("_T", bound="TomlConfig") # --------------------------------------------------------------------------- # -# Helper sentinel used to differentiate “no default supplied” from “None”. +# Helper sentinel used to differentiate "no default supplied" from "None". # --------------------------------------------------------------------------- # _LOCAL = object() @@ -47,7 +47,7 @@ class Attribute(NamedTuple): section. :type qname: str :param default_val: Default value to fall back to when the key is missing. - ``_LOCAL`` (a private sentinel) means “no default - the key is required”. + ``_LOCAL`` (a private sentinel) means "no default - the key is required". :type default_val: Any | None, optional :param section_local: If ``True`` the key is looked for only in the section defined by the concrete subclass (``self._section_``). If ``False`` the @@ -231,7 +231,7 @@ def _set_field( # ----------------------------------------------------------------- # value = config.get(qname, default_val) if value is _LOCAL: - # ``_LOCAL`` means “required but not supplied”. + # ``_LOCAL`` means "required but not supplied". raise ValueError( f"Expected '{qname}' in config or section({section}) for " + f"{self.__class__.__name__}!" diff --git a/dementor/config/util.py b/dementor/config/util.py index 919d975..b6a5b8d 100644 --- a/dementor/config/util.py +++ b/dementor/config/util.py @@ -88,11 +88,11 @@ class BytesValue: Supports the following input formats (str case): - - ``"hex:1122334455667788"`` — explicit hex prefix - - ``"ascii:1337LEET"`` — explicit ASCII prefix - - ``"1122334455667788"`` — auto-detect hex (when length matches ``2 * self.length``) - - ``"1337LEET"`` — auto-detect (try hex first, then encode) - - ``None`` — generate ``self.length`` cryptographically random bytes + - ``"hex:1122334455667788"`` -- explicit hex prefix + - ``"ascii:1337LEET"`` -- explicit ASCII prefix + - ``"1122334455667788"`` -- auto-detect hex (when length matches ``2 * self.length``) + - ``"1337LEET"`` -- auto-detect (try hex first, then encode) + - ``None`` -- generate ``self.length`` cryptographically random bytes When ``length`` is set, the result is validated to be exactly that many bytes. """ @@ -154,7 +154,7 @@ def _parse_str(self, value: str) -> bytes: if len(candidate) == self.length: return candidate except ValueError: - pass # not valid hex — fall through + pass # not valid hex -- fall through # Fallback: when length is known, the auto-detect hex path above # already handled the 2*length case; encode directly so that strings diff --git a/dementor/db/connector.py b/dementor/db/connector.py index acc1ed2..cbe8d47 100644 --- a/dementor/db/connector.py +++ b/dementor/db/connector.py @@ -165,11 +165,11 @@ def init_engine(session: SessionConfig) -> Engine | None: return create_engine(raw_path, **common) # MySQL / MariaDB / PostgreSQL: QueuePool. - # pool_pre_ping – detect dead connections before checkout. - # pool_use_lifo – reuse most-recent connection so idle ones expire + # pool_pre_ping - detect dead connections before checkout. + # pool_use_lifo - reuse most-recent connection so idle ones expire # naturally via server-side wait_timeout. - # pool_recycle – hard ceiling: close connections older than 1 hour. - # pool_timeout=5 – fail fast on exhaustion (PoolTimeoutError caught + # pool_recycle - hard ceiling: close connections older than 1 hour. + # pool_timeout=5 - fail fast on exhaustion (PoolTimeoutError caught # in model.py); hash file is the primary capture path. return create_engine( raw_path, diff --git a/dementor/filters.py b/dementor/filters.py old mode 100755 new mode 100644 diff --git a/dementor/loader.py b/dementor/loader.py old mode 100755 new mode 100644 index 8133ddb..8832d1c --- a/dementor/loader.py +++ b/dementor/loader.py @@ -306,7 +306,7 @@ def resolve_protocols( for path in protocol_paths: if not os.path.exists(path): - # Missing entries are ignored – they may be optional. + # Missing entries are ignored - they may be optional. continue if os.path.isfile(path): diff --git a/dementor/paths.py b/dementor/paths.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/__init__.py b/dementor/protocols/__init__.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/ftp.py b/dementor/protocols/ftp.py old mode 100755 new mode 100644 index 5880554..1085681 --- a/dementor/protocols/ftp.py +++ b/dementor/protocols/ftp.py @@ -110,7 +110,7 @@ class FTPHandler(BaseProtoHandler): Minimal FTP request handler. The handler sends the initial ``220`` greeting, then processes a very - small login sequence (``USER`` → ``PASS``). All other commands result + small login sequence (``USER`` -> ``PASS``). All other commands result in a ``501`` reply. :class:`ProtocolLogger` is used to attach FTP-specific metadata to log @@ -166,7 +166,7 @@ def handle_data(self, data: bytes | None, transport: socket) -> None: parts[1].decode(errors="replace").strip() if len(parts) > 1 else "" ) if not username: - self.reply(501) # Empty username → syntax error + self.reply(501) # Empty username -> syntax error continue self.reply(331) # Password required diff --git a/dementor/protocols/kerberos.py b/dementor/protocols/kerberos.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/ldap.py b/dementor/protocols/ldap.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/llmnr.py b/dementor/protocols/llmnr.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/mdns.py b/dementor/protocols/mdns.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/mysql.py b/dementor/protocols/mysql.py index 322d830..265d96f 100644 --- a/dementor/protocols/mysql.py +++ b/dementor/protocols/mysql.py @@ -344,7 +344,7 @@ class HandshakeResponse: # filler to the size of the handhshake response packet. All 0s. filler: f[bytes, Bytes(23)] = b"\0" * 23 - # login user name + # login user name username: cstr_t # opaque authentication response data generated by Authentication Method diff --git a/dementor/protocols/netbios.py b/dementor/protocols/netbios.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/ntlm.py b/dementor/protocols/ntlm.py old mode 100755 new mode 100644 index 3bc0c73..ba96fd0 --- a/dementor/protocols/ntlm.py +++ b/dementor/protocols/ntlm.py @@ -81,18 +81,18 @@ # =========================================================================== # NTLMv1 NtChallengeResponse and LmChallengeResponse are always exactly -# 24 bytes (DESL output per §6). NTLMv2 NtChallengeResponse is always -# > 24 bytes (NTProofStr(16) + variable Blob per §2.2.2.8). +# 24 bytes (DESL output per S6). NTLMv2 NtChallengeResponse is always +# > 24 bytes (NTProofStr(16) + variable Blob per S2.2.2.8). # Sole discriminator between v1 and v2; the ESS flag does NOT imply v2. NTLMV1_RESPONSE_LEN: int = 24 -# ServerChallenge nonce length (§2.2.1.2). +# ServerChallenge nonce length (S2.2.1.2). NTLM_CHALLENGE_LEN: int = 8 -# NTProofStr length in an NTLMv2 NtChallengeResponse (§3.3.2). +# NTProofStr length in an NTLMv2 NtChallengeResponse (S3.3.2). NTLM_NTPROOFSTR_LEN: int = 16 -# TargetName payload offset in CHALLENGE_MESSAGE: fixed header is 56 bytes (§2.2.1.2). +# TargetName payload offset in CHALLENGE_MESSAGE: fixed header is 56 bytes (S2.2.1.2). NTLM_CHALLENGE_MSG_DOMAIN_OFFSET: int = 56 # 16 zero bytes used as the ESS padding suffix in LmChallengeResponse and @@ -125,19 +125,19 @@ NTLM_TRANSPORT_NTLMSSP: str = "ntlmssp" # =========================================================================== -# Hash Types (MS-NLMP §3.3) +# Hash Types (MS-NLMP S3.3) # =========================================================================== # Classification is based on NT response length and LM response content. # # Type NT len LM len / content HC mode MS-NLMP ref -# ─────────── ──────── ─────────────────── ──────── ───────────────────── -# NetNTLMv1 24 any / non-dummy 5500 §3.3.1 plain DES -# NetNTLMv1-ESS 24 24 / LM[8:]==Z(16) 5500* §3.3.1 + ESS -# NetNTLMv2 > 24 n/a 5600 §3.3.2 HMAC-MD5 blob -# NetLMv2 > 24† 24 / non-null 5600† §3.3.2 LMv2 companion +# ----------- -------- ------------------- -------- --------------------- +# NetNTLMv1 24 any / non-dummy 5500 S3.3.1 plain DES +# NetNTLMv1-ESS 24 24 / LM[8:]==Z(16) 5500* S3.3.1 + ESS +# NetNTLMv2 > 24 n/a 5600 S3.3.2 HMAC-MD5 blob +# NetLMv2 > 24* 24 / non-null 5600* S3.3.2 LMv2 companion # # * Mode 5500 auto-detects ESS via LM[8:24]==Z(16); always emit raw ServerChallenge. -# † NetLMv2 is always paired with NetNTLMv2; both use -m 5600. +# * NetLMv2 is always paired with NetNTLMv2; both use -m 5600. # # Hashcat formats (module_05500.c and module_05600.c): # NetNTLMv1 user::domain:LM(48 hex):NT(48 hex):ServerChallenge(16 hex) @@ -145,17 +145,17 @@ # NetNTLMv2 user::domain:ServerChallenge(16 hex):NTProofStr(32 hex):Blob(var hex) # NetLMv2 user::domain:ServerChallenge(16 hex):LMProof(32 hex):CChal(16 hex) # -# ESS detection (§3.3.1): LmChallengeResponse = ClientChallenge(8) || Z(16). +# ESS detection (S3.3.1): LmChallengeResponse = ClientChallenge(8) || Z(16). # len==24 and LM[8:]==Z(16) is the sole reliable signal; the ESS negotiate # flag is supplementary only. For NTLM_TRANSPORT_RAW there are no flags, # so only the byte structure is checked. # # ============================================================================= -# Responder → Dementor label mapping +# Responder -> Dementor label mapping # ============================================================================= # # Responder label Dementor label Reason -# ─────────────── ─────────────────── ──────────────────────────────────────── +# --------------- ------------------- ---------------------------------------- # NTLMv1-SSP NetNTLMv1 or Responder collapses both; ESS changes the # NetNTLMv1-ESS effective challenge and must be distinct. # NTLMv2-SSP NetNTLMv2 Responder threshold: len > 60; spec minimum @@ -192,7 +192,7 @@ def NTLM_AUTH_classify( if nt_len > NTLMV1_RESPONSE_LEN: return NTLM_V2 - # ESS: per §3.3.1 ComputeResponse, LmChallengeResponse = ClientChallenge(8) || Z(16). + # ESS: per S3.3.1 ComputeResponse, LmChallengeResponse = ClientChallenge(8) || Z(16). # This mandates exactly 24 bytes; the byte structure is the sole reliable signal. # The NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is cross-checked only. try: @@ -227,7 +227,7 @@ def NTLM_AUTH_classify( # Challenge parsing is handled by BytesValue(NTLM_CHALLENGE_LEN) from -# dementor.config.util — supports hex:/ascii: prefixes, auto-detect, +# dementor.config.util -- supports hex:/ascii: prefixes, auto-detect, # and length validation in a single reusable helper. _parse_challenge = BytesValue(NTLM_CHALLENGE_LEN) @@ -328,7 +328,7 @@ def apply_config(session: SessionConfig) -> None: if session.ntlm_disable_ntlmv2: dm_logger.warning( - "NTLM DisableNTLMv2 is enabled — Level 3+ clients (all modern Windows) " + "NTLM DisableNTLMv2 is enabled -- Level 3+ clients (all modern Windows) " + "will FAIL authentication and NO hashes will be captured. " + "This only helps against pre-Vista / manually-configured Level 0-2 clients. " + "Use with caution." @@ -336,12 +336,12 @@ def apply_config(session: SessionConfig) -> None: # =========================================================================== -# Wire Encoding Helpers [MS-NLMP §2.2 and §2.2.2.5] +# Wire Encoding Helpers [MS-NLMP S2.2 and S2.2.2.5] # # NEGOTIATE_MESSAGE fields: always OEM (Unicode not yet negotiated). # CHALLENGE_MESSAGE / AUTHENTICATE_MESSAGE: governed by NegotiateFlags: -# NTLMSSP_NEGOTIATE_UNICODE (0x01) → UTF-16LE (no BOM) -# NTLM_NEGOTIATE_OEM (0x02) → cp437 baseline +# NTLMSSP_NEGOTIATE_UNICODE (0x01) -> UTF-16LE (no BOM) +# NTLM_NEGOTIATE_OEM (0x02) -> cp437 baseline # =========================================================================== @@ -394,7 +394,7 @@ def NTLM_AUTH_encode_string(string: str | None, negotiate_flags: int) -> bytes: # =========================================================================== -# Dummy LM Response Filtering [MS-NLMP §3.3.1] +# Dummy LM Response Filtering [MS-NLMP S3.3.1] # # When no LM hash is available (password > 14 chars or NoLMHash policy), # the client fills LmChallengeResponse with DESL() of a known dummy input: @@ -405,7 +405,7 @@ def NTLM_AUTH_encode_string(string: str | None, negotiate_flags: int) -> bytes: def _compute_dummy_lm_responses(server_challenge: bytes) -> set[bytes]: - """Compute the two known dummy LmChallengeResponse values (per §3.3.1). + """Compute the two known dummy LmChallengeResponse values (per S3.3.1). :param bytes server_challenge: 8-byte ServerChallenge from the CHALLENGE_MESSAGE :return: Two 24-byte DESL() outputs for the null and empty-string LM hashes. @@ -558,7 +558,7 @@ def NTLM_AUTH_to_hashcat_formats( comparisons appear in the branches below. - Dummy LM responses (DESL of null or empty-string LM hash) are discarded. - Level 2 duplication (LM == NT) omits the LM slot. - - Per §3.3.2 rule 7: when MsvAvTimestamp is present, clients set + - Per S3.3.2 rule 7: when MsvAvTimestamp is present, clients set LmChallengeResponse to Z(24); this null LMv2 is detected and skipped. """ if len(server_challenge) != NTLM_CHALLENGE_LEN: @@ -622,7 +622,7 @@ def NTLM_AUTH_to_hashcat_formats( server_challenge_hex: str = server_challenge.hex() - # NetNTLMv2: NtChallengeResponse = NTProofStr(16) + Blob(var) per §2.2.2.8 + # NetNTLMv2: NtChallengeResponse = NTProofStr(16) + Blob(var) per S2.2.2.8 # hashcat -m 5600: User::Domain:ServerChallenge:NTProofStr:Blob if hash_type == NTLM_V2: try: @@ -643,7 +643,7 @@ def NTLM_AUTH_to_hashcat_formats( return captures # NetLMv2 companion: HMAC-MD5(ResponseKeyLM, Server||Client)[0:16] || CChal(8) - # Per §3.3.2 rule 7: if MsvAvTimestamp was in the challenge, clients send Z(24). + # Per S3.3.2 rule 7: if MsvAvTimestamp was in the challenge, clients send Z(24). # hashcat -m 5600: User::Domain:ServerChallenge:LMProof:ClientChallenge try: if len(lm_response) == NTLMV1_RESPONSE_LEN: @@ -682,7 +682,7 @@ def NTLM_AUTH_to_hashcat_formats( return captures - # NetNTLMv1-ESS: per §3.3.1, ESS uses MD5(Server||Client)[0:8] as the challenge. + # NetNTLMv1-ESS: per S3.3.1, ESS uses MD5(Server||Client)[0:8] as the challenge. # Hashcat -m 5500 derives the mixed challenge internally; emit raw ServerChallenge. # LM field: ClientChallenge(8) || Z(16) = 24 bytes. if hash_type == NTLM_V1_ESS: @@ -711,20 +711,20 @@ def NTLM_AUTH_to_hashcat_formats( # LM slot is optional (0 or 48 hex chars); including a real LM response # enables the DES third-key optimisation. Two cases skip the LM slot: # 1. Level 2 duplication: client copies NT into LM (wrong one-way function). - # 2. Dummy LM: DESL() with null/empty-string hash — no crackable material. + # 2. Dummy LM: DESL() with null/empty-string hash -- no crackable material. try: nt_response_hex = nt_response.hex() lm_slot_hex: str = "" if len(lm_response) == NTLMV1_RESPONSE_LEN: if lm_response == nt_response: - # Case 1: duplication — LM is a copy of NT, skip it + # Case 1: duplication -- LM is a copy of NT, skip it dm_logger.debug( "LmChallengeResponse == NtChallengeResponse " + "(Level 2 duplication); omitting LM slot" ) elif lm_response in _compute_dummy_lm_responses(server_challenge): - # Case 2: dummy DESL output — no crackable credential material + # Case 2: dummy DESL output -- no crackable credential material dm_logger.debug( "LmChallengeResponse matches dummy LM hash; omitting LM slot" ) @@ -760,7 +760,7 @@ def NTLM_new_timestamp() -> int: :return: Current UTC time in 100-nanosecond intervals since Windows epoch (1601-01-01) :rtype: int """ - # calendar.timegm() → UTC seconds since 1970; scaled to 100ns ticks since 1601. + # calendar.timegm() -> UTC seconds since 1970; scaled to 100ns ticks since 1601. return ( NTLM_FILETIME_EPOCH_OFFSET + calendar.timegm(time.gmtime()) * NTLM_FILETIME_TICKS_PER_SECOND @@ -792,7 +792,7 @@ def NTLM_split_fqdn(fqdn: str) -> tuple[str, str]: def NTLM_AUTH_is_anonymous(token: ntlm.NTLMAuthChallengeResponse) -> bool: """Return True if the AUTHENTICATE_MESSAGE is an anonymous (null session) auth. - Per §3.2.5.1.2 server-side logic, null session is structural: + Per S3.2.5.1.2 server-side logic, null session is structural: UserName empty, NtChallengeResponse empty, and LmChallengeResponse empty or Z(1). For capture-first operation, do not trust the anonymous flag alone, and do not fail-closed on parsing exceptions. @@ -854,7 +854,7 @@ def NTLM_AUTH_CreateChallenge( """Build a CHALLENGE_MESSAGE from the client's NEGOTIATE_MESSAGE flags. :param ntlm.NTLMAuthNegotiate|dict token: Parsed NEGOTIATE_MESSAGE (must have a "flags" key) - :param str name: Server NetBIOS computer name — the flat hostname label, e.g. + :param str name: Server NetBIOS computer name -- the flat hostname label, e.g. "DEMENTOR" or "SERVER1". Must not contain a dot; callers should obtain this from NTLM_split_fqdn :param str domain: Server DNS domain name or "WORKGROUP", e.g. "corp.example.com". @@ -949,7 +949,7 @@ def NTLM_AUTH_CreateChallenge( dm_logger.debug("LM_KEY flag echoed into CHALLENGE_MESSAGE") # -- VERSION negotiation ------------------------------------------------- - # Per §2.2.1.2 and §3.2.5.1.1, Version should be populated only when + # Per S2.2.1.2 and S3.2.5.1.1, Version should be populated only when # NTLMSSP_NEGOTIATE_VERSION is negotiated; otherwise it must be all-zero. if client_flags & ntlm.NTLMSSP_NEGOTIATE_VERSION: response_flags |= ntlm.NTLMSSP_NEGOTIATE_VERSION @@ -959,7 +959,7 @@ def NTLM_AUTH_CreateChallenge( response_flags &= ~ntlm.NTLMSSP_NEGOTIATE_LM_KEY # -- Assemble the CHALLENGE_MESSAGE ------------------------------------ - # TargetName (§2.2.1.2): the server's authentication realm. A domain- + # TargetName (S2.2.1.2): the server's authentication realm. A domain- # joined server returns the NetBIOS domain name (flat, first DNS label, # uppercase); a workgroup server returns the NetBIOS computer name. # We always use the domain: NTLM_split_fqdn guarantees `domain` is @@ -979,13 +979,13 @@ def NTLM_AUTH_CreateChallenge( challenge_message["Version"] = NTLM_VERSION_PLACEHOLDER challenge_message["VersionLen"] = NTLM_VERSION_LEN - # TargetInfoFields (§2.2.1.2) sits immediately after TargetName in the + # TargetInfoFields (S2.2.1.2) sits immediately after TargetName in the # wire payload; its buffer offset is computed from TargetName's length. target_info_offset: int = NTLM_CHALLENGE_MSG_DOMAIN_OFFSET + len(target_name_bytes) if disable_ntlmv2: # Omitting TargetInfoFields prevents the client from constructing - # an NTLMv2 Blob (§3.3.2), forcing NTLMv1-capable clients to fall + # an NTLMv2 Blob (S3.3.2), forcing NTLMv1-capable clients to fall # back to NTLMv1. Level 3+ clients will refuse to authenticate. challenge_message["TargetInfoFields_len"] = 0 challenge_message["TargetInfoFields_max_len"] = 0 @@ -993,8 +993,8 @@ def NTLM_AUTH_CreateChallenge( challenge_message["TargetInfoFields_offset"] = target_info_offset dm_logger.debug("TargetInfoFields omitted (disable_ntlmv2=True)") else: - # TargetInfo is a sequence of AV_PAIR structures (§2.2.2.1). - # Full AvId space — disposition for each entry: + # TargetInfo is a sequence of AV_PAIR structures (S2.2.2.1). + # Full AvId space -- disposition for each entry: # # AvId Constant Sent Notes # 0x0000 MsvAvEOL auto List terminator; ntlm.AV_PAIRS appends it. @@ -1004,14 +1004,14 @@ def NTLM_AUTH_CreateChallenge( # 0x0004 MsvAvDnsDomainName YES DNS domain FQDN. # 0x0005 MsvAvDnsTreeName COND Forest FQDN; omitted when not domain-joined. # 0x0006 MsvAvFlags NO Constrained-auth flag (0x1); not applicable - # here — Dementor does not enforce constrained - # delegation. 0x2/0x4 bits are client→server. + # here -- Dementor does not enforce constrained + # delegation. 0x2/0x4 bits are client->server. # 0x0007 MsvAvTimestamp NO Intentionally omitted; see note below. - # 0x0008 MsvAvSingleHost N/A Client→server only (AUTHENTICATE_MESSAGE). - # 0x0009 MsvAvTargetName N/A Client→server only (AUTHENTICATE_MESSAGE). - # 0x000A MsvAvChannelBindings N/A Client→server only (AUTHENTICATE_MESSAGE). + # 0x0008 MsvAvSingleHost N/A Client->server only (AUTHENTICATE_MESSAGE). + # 0x0009 MsvAvTargetName N/A Client->server only (AUTHENTICATE_MESSAGE). + # 0x000A MsvAvChannelBindings N/A Client->server only (AUTHENTICATE_MESSAGE). # - # §2.2.2.1: 0x0001 and 0x0002 MUST be present. MsvAvEOL is + # S2.2.2.1: 0x0001 and 0x0002 MUST be present. MsvAvEOL is # appended automatically by ntlm.AV_PAIRS. AV_PAIRs may appear in # any order per spec; ascending AvId matches real Windows behaviour. @@ -1040,7 +1040,7 @@ def NTLM_AUTH_CreateChallenge( # 3. Encoding ------------------------------------------------------- # NTLM_AUTH_encode_string selects UTF-16LE or OEM based on the - # negotiated UNICODE flag in response_flags. Per §2.2.2.1 (note), + # negotiated UNICODE flag in response_flags. Per S2.2.2.1 (note), # TargetInfo AV_PAIR values MUST be Unicode regardless of the # negotiated encoding; all modern clients negotiate UNICODE, so this # is consistent in practice. @@ -1074,7 +1074,7 @@ def NTLM_AUTH_CreateChallenge( ) # MsvAvTimestamp (0x0007) is intentionally NOT included. - # Per §3.3.2 rule 7: when the server sends MsvAvTimestamp, the + # Per S3.3.2 rule 7: when the server sends MsvAvTimestamp, the # client MUST NOT send an LmChallengeResponse (sets it to Z(24)). # Omitting it ensures clients still send a real LMv2 alongside the # NetNTLMv2 response, maximising the number of captured hash types. diff --git a/dementor/protocols/quic.py b/dementor/protocols/quic.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/smtp.py b/dementor/protocols/smtp.py old mode 100755 new mode 100644 index 6202021..80a6794 --- a/dementor/protocols/smtp.py +++ b/dementor/protocols/smtp.py @@ -275,7 +275,7 @@ async def chapture_ntlm_auth(self, server: SMTPServerBase, blob=None) -> Any: self.logger, ) if self.server_config.smtp_downgrade: - # Perform a simple donẃngrade attack by sending failed authentication + # Perform a simple downgrade attack by sending failed authentication # - Some clients may choose to use fall back to other login mechanisms # provided by the server self.logger.display( diff --git a/dementor/protocols/spnego.py b/dementor/protocols/spnego.py old mode 100755 new mode 100644 diff --git a/dementor/protocols/ssdp.py b/dementor/protocols/ssdp.py index 349b76f..b6bba68 100644 --- a/dementor/protocols/ssdp.py +++ b/dementor/protocols/ssdp.py @@ -124,12 +124,12 @@ class UDN: def __init__(self, udn: str) -> None: self.tokens = udn.split(":") - # Shall begin with “uuid:” followed by a UUID suffix specified by a UPnP vendor. + # Shall begin with "uuid:" followed by a UUID suffix specified by a UPnP vendor. @property def udn_uuid(self) -> str: return self.tokens[1] - # [Table 1-1 — Root device discovery messages] + # [Table 1-1 -- Root device discovery messages] # uuid:device-UUID::upnp:rootdevice # or # uuid:device-UUID @@ -319,7 +319,7 @@ def handle_search(self, transport): header_buffer = [ SSDP_OK_H.decode(), # CACHE-CONTROL - # Required. Field value shall have the max-age directive (“max-age=”) followed by + # Required. Field value shall have the max-age directive ("max-age=") followed by # an integer that specifies the number of seconds the advertisement is valid. f"CACHE-CONTROL: max-age={self.ssdp_config.ssdp_max_age}", # EXT diff --git a/dementor/servers.py b/dementor/servers.py old mode 100755 new mode 100644 diff --git a/dementor/tui/commands/config.py b/dementor/tui/commands/config.py old mode 100755 new mode 100644 index 165fa26..6d8ebca --- a/dementor/tui/commands/config.py +++ b/dementor/tui/commands/config.py @@ -195,7 +195,7 @@ def _resolve_key_path( invalid = False if not key: - # Empty key is considered invalid – keep defaults + # Empty key is considered invalid - keep defaults invalid = True else: # Helper for case-insensitive dict lookup returning the actual key @@ -226,7 +226,7 @@ def ci_lookup(d: dict[str, Any], lookup: str) -> str | None: invalid = True break if i == len(parts) - 1: - # Final element – return the list and index + # Final element - return the list and index result_container = current result_key = idx break @@ -248,7 +248,7 @@ def ci_lookup(d: dict[str, Any], lookup: str) -> str | None: result_container = current result_key = actual_key break - # Intermediate segment – move deeper + # Intermediate segment - move deeper actual_key = ci_lookup(current, part) if actual_key is None: invalid = True diff --git a/dementor/tui/commands/database.py b/dementor/tui/commands/database.py index b25c7b8..b978294 100644 --- a/dementor/tui/commands/database.py +++ b/dementor/tui/commands/database.py @@ -310,7 +310,7 @@ def get_completions(self, word: str, document: Document) -> list[str]: except Exception: tokens = document.text_before_cursor.split() - # No sub-command yet – suggest the four possible actions. + # No sub-command yet - suggest the four possible actions. subcommands = ["creds", "hosts", "clean", "export"] if len(tokens) <= 1: return [sc for sc in subcommands if sc.startswith(word)] diff --git a/dementor/tui/commands/env.py b/dementor/tui/commands/env.py old mode 100755 new mode 100644 diff --git a/dementor/tui/commands/ipconfig.py b/dementor/tui/commands/ipconfig.py old mode 100755 new mode 100644 diff --git a/dementor/tui/completer.py b/dementor/tui/completer.py old mode 100755 new mode 100644 index 705a8a2..cc6a232 --- a/dementor/tui/completer.py +++ b/dementor/tui/completer.py @@ -92,7 +92,7 @@ def get_completions(self, document: Document, complete_event: CompleteEvent): except Exception: tokens = text_before.split() - # No tokens yet → suggest command names. + # No tokens yet -> suggest command names. if not tokens: for name in self._iter_command_names(): if name.startswith(word): diff --git a/docs/source/compat.rst b/docs/source/compat.rst index be43fd4..7db0fa2 100644 --- a/docs/source/compat.rst +++ b/docs/source/compat.rst @@ -727,7 +727,7 @@ in development. The legend for each symbol is as follows: -
[1]: Responder combines NetNTLMv1 and NetNTLMv1-ESS under a single "NTLMv1-SSP" label. This is not incorrect — hashcat -m 5500 handles both — but Dementor distinguishes them for more granular reporting. Applies to all NTLM-capable protocols (SMB, HTTP, MSSQL, LDAP, DCE/RPC).
[1]: Responder combines NetNTLMv1 and NetNTLMv1-ESS under a single "NTLMv1-SSP" label. This is not incorrect -- hashcat -m 5500 handles both -- but Dementor distinguishes them for more granular reporting. Applies to all NTLM-capable protocols (SMB, HTTP, MSSQL, LDAP, DCE/RPC).