Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/layers/ntlm.py: 35%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SPDX-License-Identifier: GPL-2.0-only
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) Gabriel Potter
6"""
7NTLM
9This is documented in [MS-NLMP]
11.. note::
12 You will find more complete documentation for this layer over at
13 `GSSAPI <https://scapy.readthedocs.io/en/latest/layers/gssapi.html#ntlm>`_
14"""
16import copy
17import time
18import os
19import struct
21from enum import IntEnum
23from scapy.asn1.asn1 import ASN1_Codecs
24from scapy.asn1.mib import conf # loads conf.mib
25from scapy.asn1fields import (
26 ASN1F_OID,
27 ASN1F_PRINTABLE_STRING,
28 ASN1F_SEQUENCE,
29 ASN1F_SEQUENCE_OF,
30)
31from scapy.asn1packet import ASN1_Packet
32from scapy.compat import bytes_base64
33from scapy.error import log_runtime
34from scapy.fields import (
35 ByteEnumField,
36 ByteField,
37 ConditionalField,
38 Field,
39 FieldLenField,
40 FlagsField,
41 LEIntEnumField,
42 LEIntField,
43 LEShortEnumField,
44 LEShortField,
45 LEThreeBytesField,
46 MultipleTypeField,
47 PacketField,
48 PacketListField,
49 StrField,
50 StrFieldUtf16,
51 StrFixedLenField,
52 StrLenFieldUtf16,
53 UTCTimeField,
54 XStrField,
55 XStrFixedLenField,
56 XStrLenField,
57 _StrField,
58)
59from scapy.packet import Packet
60from scapy.sessions import StringBuffer
62from scapy.layers.gssapi import (
63 _GSSAPI_OIDS,
64 _GSSAPI_SIGNATURE_OIDS,
65 GSS_C_FLAGS,
66 GSS_C_NO_CHANNEL_BINDINGS,
67 GSS_S_BAD_BINDINGS,
68 GSS_S_COMPLETE,
69 GSS_S_CONTINUE_NEEDED,
70 GSS_S_DEFECTIVE_CREDENTIAL,
71 GSS_S_DEFECTIVE_TOKEN,
72 GSS_S_FLAGS,
73 GssChannelBindings,
74 SSP,
75)
77# Typing imports
78from typing import (
79 Any,
80 Callable,
81 List,
82 Optional,
83 Tuple,
84 Union,
85)
87# Crypto imports
89from scapy.layers.tls.crypto.hash import Hash_MD4, Hash_MD5
90from scapy.layers.tls.crypto.h_mac import Hmac_MD5
92##########
93# Fields #
94##########
97# NTLM structures are all in all very complicated. Many fields don't have a fixed
98# position, but are rather referred to with an offset (from the beginning of the
99# structure) and a length. In addition to that, there are variants of the structure
100# with missing fields when running old versions of Windows (sometimes also seen when
101# talking to products that reimplement NTLM, most notably backup applications).
103# We add `_NTLMPayloadField` and `_NTLMPayloadPacket` to parse fields that use an
104# offset, and `_NTLM_post_build` to be able to rebuild those offsets.
105# In addition, the `NTLM_VARIANT*` allows to select what flavor of NTLM to use
106# (NT, XP, or Recent). But in real world use only Recent should be used.
109class _NTLMPayloadField(_StrField[List[Tuple[str, Any]]]):
110 """Special field used to dissect NTLM payloads.
111 This isn't trivial because the offsets are variable."""
113 __slots__ = [
114 "fields",
115 "fields_map",
116 "offset",
117 "length_from",
118 "force_order",
119 "offset_name",
120 ]
121 islist = True
123 def __init__(
124 self,
125 name, # type: str
126 offset, # type: Union[int, Callable[[Packet], int]]
127 fields, # type: List[Field[Any, Any]]
128 length_from=None, # type: Optional[Callable[[Packet], int]]
129 force_order=None, # type: Optional[List[str]]
130 offset_name="BufferOffset", # type: str
131 ):
132 # type: (...) -> None
133 self.offset = offset
134 self.fields = fields
135 self.fields_map = {field.name: field for field in fields}
136 self.length_from = length_from
137 self.force_order = force_order # whether the order of fields is fixed
138 self.offset_name = offset_name
139 super(_NTLMPayloadField, self).__init__(
140 name,
141 [
142 (field.name, field.default)
143 for field in fields
144 if field.default is not None
145 ],
146 )
148 def _on_payload(self, pkt, x, func):
149 # type: (Optional[Packet], bytes, str) -> List[Tuple[str, Any]]
150 if not pkt or not x:
151 return []
152 results = []
153 for field_name, value in x:
154 if field_name not in self.fields_map:
155 continue
156 if not isinstance(
157 self.fields_map[field_name], PacketListField
158 ) and not isinstance(value, Packet):
159 value = getattr(self.fields_map[field_name], func)(pkt, value)
160 results.append((field_name, value))
161 return results
163 def i2h(self, pkt, x):
164 # type: (Optional[Packet], bytes) -> List[Tuple[str, str]]
165 return self._on_payload(pkt, x, "i2h")
167 def h2i(self, pkt, x):
168 # type: (Optional[Packet], bytes) -> List[Tuple[str, str]]
169 return self._on_payload(pkt, x, "h2i")
171 def i2repr(self, pkt, x):
172 # type: (Optional[Packet], bytes) -> str
173 return repr(self._on_payload(pkt, x, "i2repr"))
175 def _o_pkt(self, pkt):
176 # type: (Optional[Packet]) -> int
177 if callable(self.offset):
178 return self.offset(pkt)
179 return self.offset
181 def addfield(self, pkt, s, val):
182 # type: (Optional[Packet], bytes, Optional[List[Tuple[str, str]]]) -> bytes
183 # Create string buffer
184 buf = StringBuffer()
185 buf.append(s, 1)
186 # Calc relative offset
187 r_off = self._o_pkt(pkt) - len(s)
188 if self.force_order:
189 val.sort(key=lambda x: self.force_order.index(x[0]))
190 for field_name, value in val:
191 if field_name not in self.fields_map:
192 continue
193 field = self.fields_map[field_name]
194 offset = pkt.getfieldval(field_name + self.offset_name)
195 if offset is None:
196 # No offset specified: calc
197 offset = len(buf)
198 else:
199 # Calc relative offset
200 offset -= r_off
201 pad = offset + 1 - len(buf)
202 # Add padding if necessary
203 if pad > 0:
204 buf.append(pad * b"\x00", len(buf))
205 buf.append(field.addfield(pkt, bytes(buf), value)[len(buf) :], offset + 1)
206 return bytes(buf)
208 def getfield(self, pkt, s):
209 # type: (Packet, bytes) -> Tuple[bytes, List[Tuple[str, str]]]
210 if self.length_from is None:
211 ret, remain = b"", s
212 else:
213 len_pkt = self.length_from(pkt)
214 ret, remain = s[len_pkt:], s[:len_pkt]
215 if not pkt or not remain:
216 return s, []
217 results = []
218 max_offset = 0
219 o_pkt = self._o_pkt(pkt)
220 offsets = [
221 pkt.getfieldval(x.name + self.offset_name) - o_pkt for x in self.fields
222 ]
223 for i, field in enumerate(self.fields):
224 offset = offsets[i]
225 try:
226 length = pkt.getfieldval(field.name + "Len")
227 except AttributeError:
228 length = len(remain) - offset
229 # length can't be greater than the difference with the next offset
230 try:
231 length = min(length, min(x - offset for x in offsets if x > offset))
232 except ValueError:
233 pass
234 if offset < 0:
235 continue
236 max_offset = max(offset + length, max_offset)
237 if remain[offset : offset + length]:
238 results.append(
239 (
240 offset,
241 field.name,
242 field.getfield(pkt, remain[offset : offset + length])[1],
243 )
244 )
245 ret += remain[max_offset:]
246 results.sort(key=lambda x: x[0])
247 return ret, [x[1:] for x in results]
250class _NTLMPayloadPacket(Packet):
251 _NTLM_PAYLOAD_FIELD_NAME = "Payload"
253 def __init__(
254 self,
255 _pkt=b"", # type: Union[bytes, bytearray]
256 post_transform=None, # type: Any
257 _internal=0, # type: int
258 _underlayer=None, # type: Optional[Packet]
259 _parent=None, # type: Optional[Packet]
260 **fields, # type: Any
261 ):
262 # pop unknown fields. We can't process them until the packet is initialized
263 unknown = {
264 k: fields.pop(k)
265 for k in list(fields)
266 if not any(k == f.name for f in self.fields_desc)
267 }
268 super(_NTLMPayloadPacket, self).__init__(
269 _pkt=_pkt,
270 post_transform=post_transform,
271 _internal=_internal,
272 _underlayer=_underlayer,
273 _parent=_parent,
274 **fields,
275 )
276 # check unknown fields for implicit ones
277 local_fields = next(
278 [y.name for y in x.fields]
279 for x in self.fields_desc
280 if x.name == self._NTLM_PAYLOAD_FIELD_NAME
281 )
282 implicit_fields = {k: v for k, v in unknown.items() if k in local_fields}
283 for k, value in implicit_fields.items():
284 self.setfieldval(k, value)
286 def getfieldval(self, attr):
287 # Ease compatibility with _NTLMPayloadField
288 try:
289 return super(_NTLMPayloadPacket, self).getfieldval(attr)
290 except AttributeError:
291 try:
292 return next(
293 x[1]
294 for x in super(_NTLMPayloadPacket, self).getfieldval(
295 self._NTLM_PAYLOAD_FIELD_NAME
296 )
297 if x[0] == attr
298 )
299 except StopIteration:
300 raise AttributeError(attr)
302 def getfield_and_val(self, attr):
303 # Ease compatibility with _NTLMPayloadField
304 try:
305 return super(_NTLMPayloadPacket, self).getfield_and_val(attr)
306 except ValueError:
307 PayFields = self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map
308 try:
309 return (
310 PayFields[attr],
311 PayFields[attr].h2i( # cancel out the i2h.. it's dumb i know
312 self,
313 next(
314 x[1]
315 for x in super(_NTLMPayloadPacket, self).__getattr__(
316 self._NTLM_PAYLOAD_FIELD_NAME
317 )
318 if x[0] == attr
319 ),
320 ),
321 )
322 except (StopIteration, KeyError):
323 raise ValueError(attr)
325 def setfieldval(self, attr, val):
326 # Ease compatibility with _NTLMPayloadField
327 try:
328 return super(_NTLMPayloadPacket, self).setfieldval(attr, val)
329 except AttributeError:
330 Payload = super(_NTLMPayloadPacket, self).__getattr__(
331 self._NTLM_PAYLOAD_FIELD_NAME
332 )
333 if attr not in self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map:
334 raise AttributeError(attr)
335 try:
336 Payload.pop(
337 next(
338 i
339 for i, x in enumerate(
340 super(_NTLMPayloadPacket, self).__getattr__(
341 self._NTLM_PAYLOAD_FIELD_NAME
342 )
343 )
344 if x[0] == attr
345 )
346 )
347 except StopIteration:
348 pass
349 Payload.append([attr, val])
350 super(_NTLMPayloadPacket, self).setfieldval(
351 self._NTLM_PAYLOAD_FIELD_NAME, Payload
352 )
355class _NTLM_ENUM(IntEnum):
356 LEN = 0x0001
357 MAXLEN = 0x0002
358 OFFSET = 0x0004
359 COUNT = 0x0008
360 PAD8 = 0x1000
363_NTLM_CONFIG = [
364 ("Len", _NTLM_ENUM.LEN),
365 ("MaxLen", _NTLM_ENUM.MAXLEN),
366 ("BufferOffset", _NTLM_ENUM.OFFSET),
367]
370def _NTLM_post_build(self, p, pay_offset, fields, config=_NTLM_CONFIG):
371 """Util function to build the offset and populate the lengths"""
372 for field_name, value in self.fields[self._NTLM_PAYLOAD_FIELD_NAME]:
373 fld = self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map[field_name]
374 length = fld.i2len(self, value)
375 count = fld.i2count(self, value)
376 offset = fields[field_name]
377 i = 0
378 r = lambda y: {2: "H", 4: "I", 8: "Q"}[y]
379 for fname, ftype in config:
380 if isinstance(ftype, dict):
381 ftype = ftype[field_name]
382 if ftype & _NTLM_ENUM.LEN:
383 fval = length
384 elif ftype & _NTLM_ENUM.OFFSET:
385 fval = pay_offset
386 elif ftype & _NTLM_ENUM.MAXLEN:
387 fval = length
388 elif ftype & _NTLM_ENUM.COUNT:
389 fval = count
390 else:
391 raise ValueError
392 if ftype & _NTLM_ENUM.PAD8:
393 fval += (-fval) % 8
394 sz = self.get_field(field_name + fname).sz
395 if self.getfieldval(field_name + fname) is None:
396 p = (
397 p[: offset + i]
398 + struct.pack("<%s" % r(sz), fval)
399 + p[offset + i + sz :]
400 )
401 i += sz
402 pay_offset += length
403 return p
406##############
407# Structures #
408##############
411# -- Util: VARIANT class
414class NTLM_VARIANT(IntEnum):
415 """
416 The message variant to use for NTLM.
417 """
419 NT_OR_2000 = 0
420 XP_OR_2003 = 1
421 RECENT = 2
424class _NTLM_VARIANT_Packet(_NTLMPayloadPacket):
425 def __init__(self, *args, **kwargs):
426 self.VARIANT = kwargs.pop("VARIANT", NTLM_VARIANT.RECENT)
427 super(_NTLM_VARIANT_Packet, self).__init__(*args, **kwargs)
429 def clone_with(self, *args, **kwargs):
430 pkt = super(_NTLM_VARIANT_Packet, self).clone_with(*args, **kwargs)
431 pkt.VARIANT = self.VARIANT
432 return pkt
434 def copy(self):
435 pkt = super(_NTLM_VARIANT_Packet, self).copy()
436 pkt.VARIANT = self.VARIANT
438 return pkt
440 def show2(self, dump=False, indent=3, lvl="", label_lvl=""):
441 return self.__class__(bytes(self), VARIANT=self.VARIANT).show(
442 dump, indent, lvl, label_lvl
443 )
446# Sect 2.2
449class NTLM_Header(Packet):
450 name = "NTLM Header"
451 fields_desc = [
452 StrFixedLenField("Signature", b"NTLMSSP\0", length=8),
453 LEIntEnumField(
454 "MessageType",
455 3,
456 {
457 1: "NEGOTIATE_MESSAGE",
458 2: "CHALLENGE_MESSAGE",
459 3: "AUTHENTICATE_MESSAGE",
460 },
461 ),
462 ]
464 @classmethod
465 def dispatch_hook(cls, _pkt=None, *args, **kargs):
466 if cls is NTLM_Header and _pkt and len(_pkt) >= 10:
467 MessageType = struct.unpack("<H", _pkt[8:10])[0]
468 if MessageType == 1:
469 return NTLM_NEGOTIATE
470 elif MessageType == 2:
471 return NTLM_CHALLENGE
472 elif MessageType == 3:
473 return NTLM_AUTHENTICATE_V2
474 return cls
477# Sect 2.2.2.5
478_negotiateFlags = [
479 "NEGOTIATE_UNICODE", # A
480 "NEGOTIATE_OEM", # B
481 "REQUEST_TARGET", # C
482 "r10",
483 "NEGOTIATE_SIGN", # D
484 "NEGOTIATE_SEAL", # E
485 "NEGOTIATE_DATAGRAM", # F
486 "NEGOTIATE_LM_KEY", # G
487 "r9",
488 "NEGOTIATE_NTLM", # H
489 "r8",
490 "J",
491 "NEGOTIATE_OEM_DOMAIN_SUPPLIED", # K
492 "NEGOTIATE_OEM_WORKSTATION_SUPPLIED", # L
493 "r7",
494 "NEGOTIATE_ALWAYS_SIGN", # M
495 "TARGET_TYPE_DOMAIN", # N
496 "TARGET_TYPE_SERVER", # O
497 "r6",
498 "NEGOTIATE_EXTENDED_SESSIONSECURITY", # P
499 "NEGOTIATE_IDENTIFY", # Q
500 "r5",
501 "REQUEST_NON_NT_SESSION_KEY", # R
502 "NEGOTIATE_TARGET_INFO", # S
503 "r4",
504 "NEGOTIATE_VERSION", # T
505 "r3",
506 "r2",
507 "r1",
508 "NEGOTIATE_128", # U
509 "NEGOTIATE_KEY_EXCH", # V
510 "NEGOTIATE_56", # W
511]
514def _NTLMStrField(name, default):
515 return MultipleTypeField(
516 [
517 (
518 StrFieldUtf16(name, default),
519 lambda pkt: pkt.NegotiateFlags.NEGOTIATE_UNICODE,
520 )
521 ],
522 StrField(name, default),
523 )
526# Sect 2.2.2.10
529class _NTLM_Version(Packet):
530 fields_desc = [
531 ByteField("ProductMajorVersion", 0),
532 ByteField("ProductMinorVersion", 0),
533 LEShortField("ProductBuild", 0),
534 LEThreeBytesField("res_ver", 0),
535 ByteEnumField("NTLMRevisionCurrent", 0x0F, {0x0F: "v15"}),
536 ]
539# Sect 2.2.1.1
542class NTLM_NEGOTIATE(_NTLM_VARIANT_Packet, NTLM_Header):
543 name = "NTLM Negotiate"
544 __slots__ = ["VARIANT"]
545 MessageType = 1
546 OFFSET = lambda pkt: (
547 32
548 if (
549 pkt.VARIANT == NTLM_VARIANT.NT_OR_2000
550 or (pkt.DomainNameBufferOffset or 40) <= 32
551 )
552 else 40
553 )
554 fields_desc = (
555 [
556 NTLM_Header,
557 FlagsField("NegotiateFlags", 0, -32, _negotiateFlags),
558 # DomainNameFields
559 LEShortField("DomainNameLen", None),
560 LEShortField("DomainNameMaxLen", None),
561 LEIntField("DomainNameBufferOffset", None),
562 # WorkstationFields
563 LEShortField("WorkstationNameLen", None),
564 LEShortField("WorkstationNameMaxLen", None),
565 LEIntField("WorkstationNameBufferOffset", None),
566 ]
567 + [
568 # VERSION
569 ConditionalField(
570 # (not present on some old Windows versions. We use a heuristic)
571 x,
572 lambda pkt: pkt.VARIANT >= NTLM_VARIANT.XP_OR_2003
573 and (
574 (
575 (
576 40
577 if pkt.DomainNameBufferOffset is None
578 else pkt.DomainNameBufferOffset or len(pkt.original or b"")
579 )
580 > 32
581 )
582 or pkt.fields.get(x.name, b"")
583 ),
584 )
585 for x in _NTLM_Version.fields_desc
586 ]
587 + [
588 # Payload
589 _NTLMPayloadField(
590 "Payload",
591 OFFSET,
592 [
593 _NTLMStrField("DomainName", b""),
594 _NTLMStrField("WorkstationName", b""),
595 ],
596 ),
597 ]
598 )
600 def post_build(self, pkt, pay):
601 # type: (bytes, bytes) -> bytes
602 return (
603 _NTLM_post_build(
604 self,
605 pkt,
606 self.OFFSET(),
607 {
608 "DomainName": 16,
609 "WorkstationName": 24,
610 },
611 )
612 + pay
613 )
616# Challenge
619class Single_Host_Data(Packet):
620 fields_desc = [
621 LEIntField("Size", None),
622 LEIntField("Z4", 0),
623 # "CustomData" guessed using LSAP_TOKEN_INFO_INTEGRITY.
624 FlagsField(
625 "Flags",
626 0,
627 -32,
628 {
629 0x00000001: "UAC-Restricted",
630 },
631 ),
632 LEIntEnumField(
633 "TokenIL",
634 0x00002000,
635 {
636 0x00000000: "Untrusted",
637 0x00001000: "Low",
638 0x00002000: "Medium",
639 0x00003000: "High",
640 0x00004000: "System",
641 0x00005000: "Protected process",
642 },
643 ),
644 XStrFixedLenField("MachineID", b"", length=32),
645 # KB 5068222 - still waiting for [MS-KILE] update (oct. 2025)
646 ConditionalField(
647 XStrFixedLenField("PermanentMachineID", None, length=32),
648 lambda pkt: pkt.Size is None or pkt.Size > 48,
649 ),
650 ]
652 def post_build(self, pkt, pay):
653 if self.Size is None:
654 pkt = struct.pack("<I", len(pkt)) + pkt[4:]
655 return pkt + pay
657 def default_payload_class(self, payload):
658 return conf.padding_layer
661class AV_PAIR(Packet):
662 name = "NTLM AV Pair"
663 fields_desc = [
664 LEShortEnumField(
665 "AvId",
666 0,
667 {
668 0x0000: "MsvAvEOL",
669 0x0001: "MsvAvNbComputerName",
670 0x0002: "MsvAvNbDomainName",
671 0x0003: "MsvAvDnsComputerName",
672 0x0004: "MsvAvDnsDomainName",
673 0x0005: "MsvAvDnsTreeName",
674 0x0006: "MsvAvFlags",
675 0x0007: "MsvAvTimestamp",
676 0x0008: "MsvAvSingleHost",
677 0x0009: "MsvAvTargetName",
678 0x000A: "MsvAvChannelBindings",
679 },
680 ),
681 FieldLenField("AvLen", None, length_of="Value", fmt="<H"),
682 MultipleTypeField(
683 [
684 (
685 LEIntEnumField(
686 "Value",
687 1,
688 {
689 0x0001: "constrained",
690 0x0002: "MIC integrity",
691 0x0004: "SPN from untrusted source",
692 },
693 ),
694 lambda pkt: pkt.AvId == 0x0006,
695 ),
696 (
697 UTCTimeField(
698 "Value",
699 None,
700 epoch=[1601, 1, 1, 0, 0, 0],
701 custom_scaling=1e7,
702 fmt="<Q",
703 ),
704 lambda pkt: pkt.AvId == 0x0007,
705 ),
706 (
707 PacketField("Value", Single_Host_Data(), Single_Host_Data),
708 lambda pkt: pkt.AvId == 0x0008,
709 ),
710 (
711 XStrLenField("Value", b"", length_from=lambda pkt: pkt.AvLen),
712 lambda pkt: pkt.AvId == 0x000A,
713 ),
714 ],
715 StrLenFieldUtf16("Value", b"", length_from=lambda pkt: pkt.AvLen),
716 ),
717 ]
719 def default_payload_class(self, payload):
720 return conf.padding_layer
723class NTLM_CHALLENGE(_NTLM_VARIANT_Packet, NTLM_Header):
724 name = "NTLM Challenge"
725 __slots__ = ["VARIANT"]
726 MessageType = 2
727 OFFSET = lambda pkt: (
728 48
729 if (
730 pkt.VARIANT == NTLM_VARIANT.NT_OR_2000
731 or (pkt.TargetInfoBufferOffset or 56) <= 48
732 )
733 else 56
734 )
735 fields_desc = (
736 [
737 NTLM_Header,
738 # TargetNameFields
739 LEShortField("TargetNameLen", None),
740 LEShortField("TargetNameMaxLen", None),
741 LEIntField("TargetNameBufferOffset", None),
742 #
743 FlagsField("NegotiateFlags", 0, -32, _negotiateFlags),
744 XStrFixedLenField("ServerChallenge", None, length=8),
745 XStrFixedLenField("Reserved", None, length=8),
746 # TargetInfoFields
747 LEShortField("TargetInfoLen", None),
748 LEShortField("TargetInfoMaxLen", None),
749 LEIntField("TargetInfoBufferOffset", None),
750 ]
751 + [
752 # VERSION
753 ConditionalField(
754 # (not present on some old Windows versions. We use a heuristic)
755 x,
756 lambda pkt: pkt.VARIANT >= NTLM_VARIANT.XP_OR_2003
757 and (
758 ((pkt.TargetInfoBufferOffset or 56) > 40)
759 or pkt.fields.get(x.name, b"")
760 ),
761 )
762 for x in _NTLM_Version.fields_desc
763 ]
764 + [
765 # Payload
766 _NTLMPayloadField(
767 "Payload",
768 OFFSET,
769 [
770 _NTLMStrField("TargetName", b""),
771 PacketListField("TargetInfo", [AV_PAIR()], AV_PAIR),
772 ],
773 ),
774 ]
775 )
777 def getAv(self, AvId):
778 try:
779 return next(x for x in self.TargetInfo if x.AvId == AvId)
780 except (StopIteration, AttributeError):
781 raise IndexError
783 def post_build(self, pkt, pay):
784 # type: (bytes, bytes) -> bytes
785 return (
786 _NTLM_post_build(
787 self,
788 pkt,
789 self.OFFSET(),
790 {
791 "TargetName": 12,
792 "TargetInfo": 40,
793 },
794 )
795 + pay
796 )
799# Authenticate
802class LM_RESPONSE(Packet):
803 fields_desc = [
804 StrFixedLenField("Response", b"", length=24),
805 ]
808class LMv2_RESPONSE(Packet):
809 fields_desc = [
810 StrFixedLenField("Response", b"", length=16),
811 StrFixedLenField("ChallengeFromClient", b"", length=8),
812 ]
815class NTLM_RESPONSE(Packet):
816 fields_desc = [
817 StrFixedLenField("Response", b"", length=24),
818 ]
821class NTLMv2_CLIENT_CHALLENGE(Packet):
822 fields_desc = [
823 ByteField("RespType", 1),
824 ByteField("HiRespType", 1),
825 LEShortField("Reserved1", 0),
826 LEIntField("Reserved2", 0),
827 UTCTimeField(
828 "TimeStamp", None, fmt="<Q", epoch=[1601, 1, 1, 0, 0, 0], custom_scaling=1e7
829 ),
830 StrFixedLenField("ChallengeFromClient", b"12345678", length=8),
831 LEIntField("Reserved3", 0),
832 PacketListField("AvPairs", [AV_PAIR()], AV_PAIR),
833 ]
835 def getAv(self, AvId):
836 try:
837 return next(x for x in self.AvPairs if x.AvId == AvId)
838 except StopIteration:
839 raise IndexError
842class NTLMv2_RESPONSE(NTLMv2_CLIENT_CHALLENGE):
843 fields_desc = [
844 XStrFixedLenField("NTProofStr", b"", length=16),
845 NTLMv2_CLIENT_CHALLENGE,
846 ]
848 def computeNTProofStr(self, ResponseKeyNT, ServerChallenge):
849 """
850 Set temp to ConcatenationOf(Responserversion, HiResponserversion,
851 Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4))
852 Set NTProofStr to HMAC_MD5(ResponseKeyNT,
853 ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge,temp))
855 Remember ServerName = AvPairs
856 """
857 Responserversion = b"\x01"
858 HiResponserversion = b"\x01"
860 ServerName = b"".join(bytes(x) for x in self.AvPairs)
861 temp = b"".join(
862 [
863 Responserversion,
864 HiResponserversion,
865 b"\x00" * 6,
866 struct.pack("<Q", self.TimeStamp),
867 self.ChallengeFromClient,
868 b"\x00" * 4,
869 ServerName,
870 # Final Z(4) is the EOL AvPair
871 ]
872 )
873 return HMAC_MD5(ResponseKeyNT, ServerChallenge + temp)
876class NTLM_AUTHENTICATE(_NTLM_VARIANT_Packet, NTLM_Header):
877 name = "NTLM Authenticate"
878 __slots__ = ["VARIANT"]
879 MessageType = 3
880 NTLM_VERSION = 1
881 OFFSET = lambda pkt: (
882 64
883 if (
884 pkt.VARIANT == NTLM_VARIANT.NT_OR_2000
885 or (pkt.DomainNameBufferOffset or 88) <= 64
886 )
887 else (
888 72
889 if pkt.VARIANT == NTLM_VARIANT.XP_OR_2003
890 or ((pkt.DomainNameBufferOffset or 88) <= 72)
891 else 88
892 )
893 )
894 fields_desc = (
895 [
896 NTLM_Header,
897 # LmChallengeResponseFields
898 LEShortField("LmChallengeResponseLen", None),
899 LEShortField("LmChallengeResponseMaxLen", None),
900 LEIntField("LmChallengeResponseBufferOffset", None),
901 # NtChallengeResponseFields
902 LEShortField("NtChallengeResponseLen", None),
903 LEShortField("NtChallengeResponseMaxLen", None),
904 LEIntField("NtChallengeResponseBufferOffset", None),
905 # DomainNameFields
906 LEShortField("DomainNameLen", None),
907 LEShortField("DomainNameMaxLen", None),
908 LEIntField("DomainNameBufferOffset", None),
909 # UserNameFields
910 LEShortField("UserNameLen", None),
911 LEShortField("UserNameMaxLen", None),
912 LEIntField("UserNameBufferOffset", None),
913 # WorkstationFields
914 LEShortField("WorkstationLen", None),
915 LEShortField("WorkstationMaxLen", None),
916 LEIntField("WorkstationBufferOffset", None),
917 # EncryptedRandomSessionKeyFields
918 LEShortField("EncryptedRandomSessionKeyLen", None),
919 LEShortField("EncryptedRandomSessionKeyMaxLen", None),
920 LEIntField("EncryptedRandomSessionKeyBufferOffset", None),
921 # NegotiateFlags
922 FlagsField("NegotiateFlags", 0, -32, _negotiateFlags),
923 # VERSION
924 ]
925 + [
926 ConditionalField(
927 # (not present on some old Windows versions. We use a heuristic)
928 x,
929 lambda pkt: pkt.VARIANT >= NTLM_VARIANT.XP_OR_2003
930 and (
931 ((pkt.DomainNameBufferOffset or 88) > 64)
932 or pkt.fields.get(x.name, b"")
933 ),
934 )
935 for x in _NTLM_Version.fields_desc
936 ]
937 + [
938 # MIC
939 ConditionalField(
940 # (not present on some old Windows versions. We use a heuristic)
941 XStrFixedLenField("MIC", b"", length=16),
942 lambda pkt: pkt.VARIANT >= NTLM_VARIANT.RECENT
943 and (
944 ((pkt.DomainNameBufferOffset or 88) > 72)
945 or pkt.fields.get("MIC", b"")
946 ),
947 ),
948 # Payload
949 _NTLMPayloadField(
950 "Payload",
951 OFFSET,
952 [
953 MultipleTypeField(
954 [
955 (
956 PacketField(
957 "LmChallengeResponse",
958 LMv2_RESPONSE(),
959 LMv2_RESPONSE,
960 ),
961 lambda pkt: pkt.NTLM_VERSION == 2,
962 )
963 ],
964 PacketField("LmChallengeResponse", LM_RESPONSE(), LM_RESPONSE),
965 ),
966 MultipleTypeField(
967 [
968 (
969 PacketField(
970 "NtChallengeResponse",
971 NTLMv2_RESPONSE(),
972 NTLMv2_RESPONSE,
973 ),
974 lambda pkt: pkt.NTLM_VERSION == 2,
975 )
976 ],
977 PacketField(
978 "NtChallengeResponse", NTLM_RESPONSE(), NTLM_RESPONSE
979 ),
980 ),
981 _NTLMStrField("DomainName", b""),
982 _NTLMStrField("UserName", b""),
983 _NTLMStrField("Workstation", b""),
984 XStrField("EncryptedRandomSessionKey", b""),
985 ],
986 ),
987 ]
988 )
990 def post_build(self, pkt, pay):
991 # type: (bytes, bytes) -> bytes
992 return (
993 _NTLM_post_build(
994 self,
995 pkt,
996 self.OFFSET(),
997 {
998 "LmChallengeResponse": 12,
999 "NtChallengeResponse": 20,
1000 "DomainName": 28,
1001 "UserName": 36,
1002 "Workstation": 44,
1003 "EncryptedRandomSessionKey": 52,
1004 },
1005 )
1006 + pay
1007 )
1009 def compute_mic(self, ExportedSessionKey, negotiate, challenge):
1010 self.MIC = b"\x00" * 16
1011 self.MIC = HMAC_MD5(
1012 ExportedSessionKey, bytes(negotiate) + bytes(challenge) + bytes(self)
1013 )
1016class NTLM_AUTHENTICATE_V2(NTLM_AUTHENTICATE):
1017 NTLM_VERSION = 2
1020def HTTP_ntlm_negotiate(ntlm_negotiate):
1021 """Create an HTTP NTLM negotiate packet from an NTLM_NEGOTIATE message"""
1022 assert isinstance(ntlm_negotiate, NTLM_NEGOTIATE)
1023 from scapy.layers.http import HTTP, HTTPRequest
1025 return HTTP() / HTTPRequest(
1026 Authorization=b"NTLM " + bytes_base64(bytes(ntlm_negotiate))
1027 )
1030# Experimental - Reversed stuff
1032# This is the GSSAPI NegoEX Exchange metadata blob. This is not documented
1033# but described as an "opaque blob": this was reversed and everything is a
1034# placeholder.
1037class NEGOEX_EXCHANGE_NTLM_ITEM(ASN1_Packet):
1038 ASN1_codec = ASN1_Codecs.BER
1039 ASN1_root = ASN1F_SEQUENCE(
1040 ASN1F_SEQUENCE(
1041 ASN1F_SEQUENCE(
1042 ASN1F_OID("oid", ""),
1043 ASN1F_PRINTABLE_STRING("token", ""),
1044 explicit_tag=0x31,
1045 ),
1046 explicit_tag=0x80,
1047 )
1048 )
1051class NEGOEX_EXCHANGE_NTLM(ASN1_Packet):
1052 """
1053 GSSAPI NegoEX Exchange metadata blob
1054 This was reversed and may be meaningless
1055 """
1057 ASN1_codec = ASN1_Codecs.BER
1058 ASN1_root = ASN1F_SEQUENCE(
1059 ASN1F_SEQUENCE(
1060 ASN1F_SEQUENCE_OF("items", [], NEGOEX_EXCHANGE_NTLM_ITEM), implicit_tag=0xA0
1061 ),
1062 )
1065# Crypto - [MS-NLMP]
1068def HMAC_MD5(key, data):
1069 return Hmac_MD5(key=key).digest(data)
1072def MD4le(x):
1073 """
1074 MD4 over a string encoded as utf-16le
1075 """
1076 return Hash_MD4().digest(x.encode("utf-16le"))
1079def RC4Init(key):
1080 """Alleged RC4"""
1081 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
1083 try:
1084 # cryptography > 43.0
1085 from cryptography.hazmat.decrepit.ciphers import (
1086 algorithms as decrepit_algorithms,
1087 )
1088 except ImportError:
1089 decrepit_algorithms = algorithms
1091 algorithm = decrepit_algorithms.ARC4(key)
1092 cipher = Cipher(algorithm, mode=None)
1093 encryptor = cipher.encryptor()
1094 return encryptor
1097def RC4(handle, data):
1098 """The RC4 Encryption Algorithm"""
1099 return handle.update(data)
1102def RC4K(key, data):
1103 """Indicates the encryption of data item D with the key K using the
1104 RC4 algorithm.
1105 """
1106 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
1108 try:
1109 # cryptography > 43.0
1110 from cryptography.hazmat.decrepit.ciphers import (
1111 algorithms as decrepit_algorithms,
1112 )
1113 except ImportError:
1114 decrepit_algorithms = algorithms
1116 algorithm = decrepit_algorithms.ARC4(key)
1117 cipher = Cipher(algorithm, mode=None)
1118 encryptor = cipher.encryptor()
1119 return encryptor.update(data) + encryptor.finalize()
1122# sect 2.2.2.9 - With Extended Session Security
1125class NTLMSSP_MESSAGE_SIGNATURE(Packet):
1126 # [MS-RPCE] sect 2.2.2.9.1/2.2.2.9.2
1127 fields_desc = [
1128 LEIntField("Version", 0x00000001),
1129 XStrFixedLenField("Checksum", b"", length=8),
1130 LEIntField("SeqNum", 0x00000000),
1131 ]
1133 def default_payload_class(self, payload):
1134 return conf.padding_layer
1137_GSSAPI_OIDS["1.3.6.1.4.1.311.2.2.10"] = NTLM_Header
1138_GSSAPI_SIGNATURE_OIDS["1.3.6.1.4.1.311.2.2.10"] = NTLMSSP_MESSAGE_SIGNATURE
1141# sect 3.3.2
1144def NTOWFv2(Passwd, User, UserDom, HashNt=None):
1145 """
1146 Computes the ResponseKeyNT (per [MS-NLMP] sect 3.3.2)
1148 :param Passwd: the plain password
1149 :param User: the username
1150 :param UserDom: the domain name
1151 :param HashNt: (out of spec) if you have the HashNt, use this and set
1152 Passwd to None
1153 """
1154 if HashNt is None:
1155 HashNt = MD4le(Passwd)
1156 return HMAC_MD5(HashNt, (User.upper() + UserDom).encode("utf-16le"))
1159def NTLMv2_ComputeSessionBaseKey(ResponseKeyNT, NTProofStr):
1160 return HMAC_MD5(ResponseKeyNT, NTProofStr)
1163# sect 3.4.4.2 - With Extended Session Security
1166def MAC(Handle, SigningKey, SeqNum, Message):
1167 chksum = HMAC_MD5(SigningKey, struct.pack("<i", SeqNum) + Message)[:8]
1168 if Handle:
1169 chksum = RC4(Handle, chksum)
1170 return NTLMSSP_MESSAGE_SIGNATURE(
1171 Version=0x00000001,
1172 Checksum=chksum,
1173 SeqNum=SeqNum,
1174 )
1177# sect 3.4.2
1180def SIGN(Handle, SigningKey, SeqNum, Message):
1181 # append? where is this used?!
1182 return Message + MAC(Handle, SigningKey, SeqNum, Message)
1185# sect 3.4.3
1188def SEAL(Handle, SigningKey, SeqNum, Message):
1189 """
1190 SEAL() according to [MS-NLMP]
1191 """
1192 # this is unused. Use GSS_WrapEx
1193 sealed_message = RC4(Handle, Message)
1194 signature = MAC(Handle, SigningKey, SeqNum, Message)
1195 return sealed_message, signature
1198def UNSEAL(Handle, SigningKey, SeqNum, Message):
1199 """
1200 UNSEAL() according to [MS-NLMP]
1201 """
1202 # this is unused. Use GSS_UnwrapEx
1203 unsealed_message = RC4(Handle, Message)
1204 signature = MAC(Handle, SigningKey, SeqNum, Message)
1205 return unsealed_message, signature
1208# sect 3.4.5.2
1211def SIGNKEY(NegFlg, ExportedSessionKey, Mode):
1212 if NegFlg.NEGOTIATE_EXTENDED_SESSIONSECURITY:
1213 if Mode == "Client":
1214 return Hash_MD5().digest(
1215 ExportedSessionKey
1216 + b"session key to client-to-server signing key magic constant\x00"
1217 )
1218 elif Mode == "Server":
1219 return Hash_MD5().digest(
1220 ExportedSessionKey
1221 + b"session key to server-to-client signing key magic constant\x00"
1222 )
1223 else:
1224 raise ValueError("Unknown Mode")
1225 else:
1226 return None
1229# sect 3.4.5.3
1232def SEALKEY(NegFlg, ExportedSessionKey, Mode):
1233 if NegFlg.NEGOTIATE_EXTENDED_SESSIONSECURITY:
1234 if NegFlg.NEGOTIATE_128:
1235 SealKey = ExportedSessionKey
1236 elif NegFlg.NEGOTIATE_56:
1237 SealKey = ExportedSessionKey[:7]
1238 else:
1239 SealKey = ExportedSessionKey[:5]
1240 if Mode == "Client":
1241 return Hash_MD5().digest(
1242 SealKey
1243 + b"session key to client-to-server sealing key magic constant\x00"
1244 )
1245 elif Mode == "Server":
1246 return Hash_MD5().digest(
1247 SealKey
1248 + b"session key to server-to-client sealing key magic constant\x00"
1249 )
1250 else:
1251 raise ValueError("Unknown Mode")
1252 elif NegFlg.NEGOTIATE_LM_KEY:
1253 if NegFlg.NEGOTIATE_56:
1254 return ExportedSessionKey[:6] + b"\xa0"
1255 else:
1256 return ExportedSessionKey[:4] + b"\xe5\x38\xb0"
1257 else:
1258 return ExportedSessionKey
1261# --- SSP
1264class NTLMSSP(SSP):
1265 """
1266 The NTLM SSP
1268 Common arguments:
1270 :param auth_level: One of DCE_C_AUTHN_LEVEL
1271 :param USE_MIC: whether to use a MIC or not (default: True)
1272 :param NTLM_VALUES: a dictionary used to override the following values
1274 In case of a client::
1276 - NegotiateFlags
1277 - ProductMajorVersion
1278 - ProductMinorVersion
1279 - ProductBuild
1281 In case of a server::
1283 - NetbiosDomainName
1284 - NetbiosComputerName
1285 - DnsComputerName
1286 - DnsDomainName (defaults to DOMAIN)
1287 - DnsTreeName (defaults to DOMAIN)
1288 - Flags
1289 - Timestamp
1291 Client-only arguments:
1293 :param UPN: the UPN to use for NTLM auth. If no domain is specified, will
1294 use the one provided by the server (domain in a domain, local
1295 if without domain)
1296 :param HASHNT: the password to use for NTLM auth
1297 :param PASSWORD: the password to use for NTLM auth
1299 Server-only arguments:
1301 :param DOMAIN_FQDN: the domain FQDN (default: domain.local)
1302 :param DOMAIN_NB_NAME: the domain Netbios name (default: strip DOMAIN_FQDN)
1303 :param COMPUTER_NB_NAME: the server Netbios name (default: SRV)
1304 :param COMPUTER_FQDN: the server FQDN
1305 (default: <computer_nb_name>.<domain_fqdn>)
1306 :param IDENTITIES: a dict {"username": <HashNT>}
1307 Setting this value enables signature computation and
1308 authenticates inbound users.
1309 """
1311 auth_type = 0x0A
1313 class STATE(SSP.STATE):
1314 INIT = 1
1315 CLI_SENT_NEGO = 2
1316 CLI_SENT_AUTH = 3
1317 SRV_SENT_CHAL = 4
1319 class CONTEXT(SSP.CONTEXT):
1320 __slots__ = [
1321 "SessionKey",
1322 "ExportedSessionKey",
1323 "IsAcceptor",
1324 "SendSignKey",
1325 "SendSealKey",
1326 "RecvSignKey",
1327 "RecvSealKey",
1328 "SendSealHandle",
1329 "RecvSealHandle",
1330 "SendSeqNum",
1331 "RecvSeqNum",
1332 "neg_tok",
1333 "chall_tok",
1334 "ServerHostname",
1335 "ServerDomain",
1336 ]
1338 def __init__(self, IsAcceptor, req_flags=None):
1339 self.state = NTLMSSP.STATE.INIT
1340 self.SessionKey = None
1341 self.ExportedSessionKey = None
1342 self.SendSignKey = None
1343 self.SendSealKey = None
1344 self.SendSealHandle = None
1345 self.RecvSignKey = None
1346 self.RecvSealKey = None
1347 self.RecvSealHandle = None
1348 self.SendSeqNum = 0
1349 self.RecvSeqNum = 0
1350 self.neg_tok = None
1351 self.chall_tok = None
1352 self.ServerHostname = None
1353 self.ServerDomain = None
1354 self.IsAcceptor = IsAcceptor
1355 super(NTLMSSP.CONTEXT, self).__init__(req_flags=req_flags)
1357 def clifailure(self):
1358 self.__init__(self.IsAcceptor, req_flags=self.flags)
1360 def __repr__(self):
1361 return "NTLMSSP"
1363 # [MS-NLMP] note <36>: "the maximum lifetime is 36 hours" (lol, Kerberos has 5min)
1364 NTLM_MaxLifetime = 36 * 3600
1366 def __init__(
1367 self,
1368 UPN=None,
1369 HASHNT=None,
1370 PASSWORD=None,
1371 USE_MIC=True,
1372 VARIANT: NTLM_VARIANT = NTLM_VARIANT.RECENT,
1373 NTLM_VALUES={},
1374 DOMAIN_FQDN=None,
1375 DOMAIN_NB_NAME=None,
1376 COMPUTER_NB_NAME=None,
1377 COMPUTER_FQDN=None,
1378 IDENTITIES=None,
1379 DO_NOT_CHECK_LOGIN=False,
1380 SERVER_CHALLENGE=None,
1381 **kwargs,
1382 ):
1383 self.UPN = UPN
1384 if HASHNT is None and PASSWORD is not None:
1385 HASHNT = MD4le(PASSWORD)
1386 self.HASHNT = HASHNT
1387 self.VARIANT = VARIANT
1388 if self.VARIANT != NTLM_VARIANT.RECENT:
1389 log_runtime.warning(
1390 "VARIANT != NTLM_VARIANT.RECENT. You shouldn't touch this !"
1391 )
1392 self.USE_MIC = False
1393 else:
1394 self.USE_MIC = USE_MIC
1395 self.NTLM_VALUES = NTLM_VALUES
1396 if UPN is not None:
1397 # Populate values used only in server mode.
1398 from scapy.layers.kerberos import _parse_upn
1400 try:
1401 user, realm = _parse_upn(UPN)
1402 if DOMAIN_FQDN is None:
1403 DOMAIN_FQDN = realm
1404 if COMPUTER_NB_NAME is None:
1405 COMPUTER_NB_NAME = user
1406 except ValueError:
1407 pass
1409 # Compute various netbios/fqdn names
1410 self.DOMAIN_FQDN = DOMAIN_FQDN or "domain.local"
1411 self.DOMAIN_NB_NAME = (
1412 DOMAIN_NB_NAME or self.DOMAIN_FQDN.split(".")[0].upper()[:15]
1413 )
1414 self.COMPUTER_NB_NAME = COMPUTER_NB_NAME or "WIN10"
1415 self.COMPUTER_FQDN = COMPUTER_FQDN or (
1416 self.COMPUTER_NB_NAME.lower() + "." + self.DOMAIN_FQDN
1417 )
1419 if IDENTITIES:
1420 self.IDENTITIES = {
1421 # Windows usernames are case insensitive
1422 user.upper(): hashnt
1423 for user, hashnt in IDENTITIES.items()
1424 }
1425 else:
1426 self.IDENTITIES = IDENTITIES
1428 self.DO_NOT_CHECK_LOGIN = DO_NOT_CHECK_LOGIN
1429 self.SERVER_CHALLENGE = SERVER_CHALLENGE
1430 super(NTLMSSP, self).__init__(**kwargs)
1432 def LegsAmount(self, Context: CONTEXT):
1433 return 3
1435 def GSS_Inquire_names_for_mech(self):
1436 return ["1.3.6.1.4.1.311.2.2.10"]
1438 def GSS_GetMICEx(self, Context, msgs, qop_req=0):
1439 """
1440 [MS-NLMP] sect 3.4.8
1441 """
1442 # Concatenate the ToSign
1443 ToSign = b"".join(x.data for x in msgs if x.sign)
1444 sig = MAC(
1445 Context.SendSealHandle,
1446 Context.SendSignKey,
1447 Context.SendSeqNum,
1448 ToSign,
1449 )
1450 Context.SendSeqNum += 1
1451 return sig
1453 def GSS_VerifyMICEx(self, Context, msgs, signature):
1454 """
1455 [MS-NLMP] sect 3.4.9
1456 """
1457 Context.RecvSeqNum = signature.SeqNum
1458 # Concatenate the ToSign
1459 ToSign = b"".join(x.data for x in msgs if x.sign)
1460 sig = MAC(
1461 Context.RecvSealHandle,
1462 Context.RecvSignKey,
1463 Context.RecvSeqNum,
1464 ToSign,
1465 )
1466 if sig.Checksum != signature.Checksum:
1467 raise ValueError("ERROR: Checksums don't match")
1469 def GSS_WrapEx(self, Context, msgs, qop_req=0):
1470 """
1471 [MS-NLMP] sect 3.4.6
1472 """
1473 msgs_cpy = copy.deepcopy(msgs) # Keep copy for signature
1474 # Encrypt
1475 for msg in msgs:
1476 if msg.conf_req_flag:
1477 msg.data = RC4(Context.SendSealHandle, msg.data)
1478 # Sign
1479 sig = self.GSS_GetMICEx(Context, msgs_cpy, qop_req=qop_req)
1480 return (
1481 msgs,
1482 sig,
1483 )
1485 def GSS_UnwrapEx(self, Context, msgs, signature):
1486 """
1487 [MS-NLMP] sect 3.4.7
1488 """
1489 # Decrypt
1490 for msg in msgs:
1491 if msg.conf_req_flag:
1492 msg.data = RC4(Context.RecvSealHandle, msg.data)
1493 # Check signature
1494 self.GSS_VerifyMICEx(Context, msgs, signature)
1495 return msgs
1497 def SupportsMechListMIC(self):
1498 if not self.USE_MIC:
1499 # RFC 4178
1500 # "If the mechanism selected by the negotiation does not support integrity
1501 # protection, then no mechlistMIC token is used."
1502 return False
1503 if self.DO_NOT_CHECK_LOGIN:
1504 # In this mode, we won't negotiate any credentials.
1505 return False
1506 return True
1508 def GetMechListMIC(self, Context, input):
1509 # [MS-SPNG]
1510 # "When NTLM is negotiated, the SPNG server MUST set OriginalHandle to
1511 # ServerHandle before generating the mechListMIC, then set ServerHandle to
1512 # OriginalHandle after generating the mechListMIC."
1513 OriginalHandle = Context.SendSealHandle
1514 Context.SendSealHandle = RC4Init(Context.SendSealKey)
1515 try:
1516 return super(NTLMSSP, self).GetMechListMIC(Context, input)
1517 finally:
1518 Context.SendSealHandle = OriginalHandle
1520 def VerifyMechListMIC(self, Context, otherMIC, input):
1521 # [MS-SPNG]
1522 # "the SPNEGO Extension server MUST set OriginalHandle to ClientHandle before
1523 # validating the mechListMIC and then set ClientHandle to OriginalHandle after
1524 # validating the mechListMIC."
1525 OriginalHandle = Context.RecvSealHandle
1526 Context.RecvSealHandle = RC4Init(Context.RecvSealKey)
1527 try:
1528 return super(NTLMSSP, self).VerifyMechListMIC(Context, otherMIC, input)
1529 finally:
1530 Context.RecvSealHandle = OriginalHandle
1532 def GSS_Init_sec_context(
1533 self,
1534 Context: CONTEXT,
1535 input_token=None,
1536 target_name: Optional[str] = None,
1537 req_flags: Optional[GSS_C_FLAGS] = None,
1538 chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS,
1539 ):
1540 if Context is None:
1541 Context = self.CONTEXT(False, req_flags=req_flags)
1543 if Context.state == self.STATE.INIT:
1544 # Client: negotiate
1545 # Create a default token
1546 tok = NTLM_NEGOTIATE(
1547 VARIANT=self.VARIANT,
1548 NegotiateFlags="+".join(
1549 [
1550 "NEGOTIATE_UNICODE",
1551 "REQUEST_TARGET",
1552 "NEGOTIATE_NTLM",
1553 "NEGOTIATE_ALWAYS_SIGN",
1554 "TARGET_TYPE_DOMAIN",
1555 "NEGOTIATE_EXTENDED_SESSIONSECURITY",
1556 "NEGOTIATE_TARGET_INFO",
1557 "NEGOTIATE_128",
1558 "NEGOTIATE_56",
1559 ]
1560 + (
1561 ["NEGOTIATE_VERSION"]
1562 if self.VARIANT >= NTLM_VARIANT.XP_OR_2003
1563 else []
1564 )
1565 + (
1566 [
1567 "NEGOTIATE_KEY_EXCH",
1568 ]
1569 if Context.flags
1570 & (GSS_C_FLAGS.GSS_C_INTEG_FLAG | GSS_C_FLAGS.GSS_C_CONF_FLAG)
1571 else []
1572 )
1573 + (
1574 [
1575 "NEGOTIATE_SIGN",
1576 ]
1577 if Context.flags & GSS_C_FLAGS.GSS_C_INTEG_FLAG
1578 else []
1579 )
1580 + (
1581 [
1582 "NEGOTIATE_SEAL",
1583 ]
1584 if Context.flags & GSS_C_FLAGS.GSS_C_CONF_FLAG
1585 else []
1586 )
1587 + (
1588 [
1589 "NEGOTIATE_IDENTIFY",
1590 ]
1591 if Context.flags & GSS_C_FLAGS.GSS_C_IDENTIFY_FLAG
1592 else []
1593 )
1594 ),
1595 ProductMajorVersion=10,
1596 ProductMinorVersion=0,
1597 ProductBuild=19041,
1598 )
1599 if self.NTLM_VALUES:
1600 # Update that token with the customs one
1601 for key in [
1602 "NegotiateFlags",
1603 "ProductMajorVersion",
1604 "ProductMinorVersion",
1605 "ProductBuild",
1606 ]:
1607 if key in self.NTLM_VALUES:
1608 setattr(tok, key, self.NTLM_VALUES[key])
1609 Context.neg_tok = tok
1610 Context.SessionKey = None # Reset signing (if previous auth failed)
1611 Context.state = self.STATE.CLI_SENT_NEGO
1612 return Context, tok, GSS_S_CONTINUE_NEEDED
1613 elif Context.state == self.STATE.CLI_SENT_NEGO:
1614 # Client: auth (token=challenge)
1615 chall_tok = input_token
1616 if self.UPN is None or self.HASHNT is None:
1617 raise ValueError(
1618 "Must provide a 'UPN' and a 'HASHNT' or 'PASSWORD' when "
1619 "running in standalone !"
1620 )
1622 from scapy.layers.kerberos import _parse_upn
1624 # Check token sanity
1625 if not chall_tok or NTLM_CHALLENGE not in chall_tok:
1626 log_runtime.debug("NTLMSSP: Unexpected token. Expected NTLM Challenge")
1627 return Context, None, GSS_S_DEFECTIVE_TOKEN
1629 # Some information from the CHALLENGE are stored
1630 try:
1631 Context.ServerHostname = chall_tok.getAv(0x0001).Value
1632 except IndexError:
1633 pass
1634 try:
1635 Context.ServerDomain = chall_tok.getAv(0x0002).Value
1636 except IndexError:
1637 pass
1638 try:
1639 # the server SHOULD set the timestamp in the CHALLENGE_MESSAGE
1640 ServerTimestamp = chall_tok.getAv(0x0007).Value
1641 ServerTime = (ServerTimestamp / 1e7) - 11644473600
1643 if abs(ServerTime - time.time()) >= NTLMSSP.NTLM_MaxLifetime:
1644 log_runtime.warning(
1645 "Server and Client times are off by more than 36h !"
1646 )
1647 # We could error here, but we don't.
1648 except IndexError:
1649 pass
1651 # Initialize a default token
1652 tok = NTLM_AUTHENTICATE_V2(
1653 VARIANT=self.VARIANT,
1654 NegotiateFlags=chall_tok.NegotiateFlags,
1655 ProductMajorVersion=10,
1656 ProductMinorVersion=0,
1657 ProductBuild=19041,
1658 )
1659 tok.LmChallengeResponse = LMv2_RESPONSE()
1661 # Populate the token
1662 # 1. Set username
1663 try:
1664 tok.UserName, realm = _parse_upn(self.UPN)
1665 except ValueError:
1666 tok.UserName, realm = self.UPN, Context.ServerDomain
1668 # 2. Set domain name
1669 if realm is None:
1670 log_runtime.warning(
1671 "No realm specified in UPN, nor provided by server."
1672 )
1673 tok.DomainName = self.DOMAIN_FQDN
1674 else:
1675 tok.DomainName = realm
1677 # 3. Set workstation name
1678 tok.Workstation = self.COMPUTER_NB_NAME
1680 # 4. Create and calculate the ChallengeResponse
1681 # 4.1 Build the payload
1682 cr = tok.NtChallengeResponse = NTLMv2_RESPONSE(
1683 ChallengeFromClient=os.urandom(8),
1684 )
1685 cr.TimeStamp = int((time.time() + 11644473600) * 1e7)
1686 cr.AvPairs = (
1687 # Repeat AvPairs from the server
1688 chall_tok.TargetInfo[:-1]
1689 + (
1690 [
1691 AV_PAIR(AvId="MsvAvFlags", Value="MIC integrity"),
1692 ]
1693 if self.USE_MIC
1694 else []
1695 )
1696 + [
1697 AV_PAIR(
1698 AvId="MsvAvSingleHost",
1699 Value=Single_Host_Data(
1700 MachineID=os.urandom(32),
1701 PermanentMachineID=os.urandom(32),
1702 ),
1703 ),
1704 ]
1705 + (
1706 [
1707 AV_PAIR(
1708 # [MS-NLMP] sect 2.2.2.1 refers to RFC 4121 sect 4.1.1.2
1709 # "The Bnd field contains the MD5 hash of channel bindings"
1710 AvId="MsvAvChannelBindings",
1711 Value=chan_bindings.digestMD5(),
1712 ),
1713 ]
1714 if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS
1715 else []
1716 )
1717 + [
1718 AV_PAIR(
1719 AvId="MsvAvTargetName",
1720 Value=target_name or ("host/" + Context.ServerHostname),
1721 ),
1722 AV_PAIR(AvId="MsvAvEOL"),
1723 ]
1724 )
1725 if self.NTLM_VALUES:
1726 # Update that token with the customs one
1727 for key in [
1728 "NegotiateFlags",
1729 "ProductMajorVersion",
1730 "ProductMinorVersion",
1731 "ProductBuild",
1732 ]:
1733 if key in self.NTLM_VALUES:
1734 setattr(tok, key, self.NTLM_VALUES[key])
1736 # 4.2 Compute the ResponseKeyNT
1737 ResponseKeyNT = NTOWFv2(
1738 None,
1739 tok.UserName,
1740 tok.DomainName,
1741 HashNt=self.HASHNT,
1742 )
1744 # 4.3 Compute the NTProofStr
1745 cr.NTProofStr = cr.computeNTProofStr(
1746 ResponseKeyNT,
1747 chall_tok.ServerChallenge,
1748 )
1750 # 4.4 Compute the Session Key
1751 SessionBaseKey = NTLMv2_ComputeSessionBaseKey(ResponseKeyNT, cr.NTProofStr)
1752 KeyExchangeKey = SessionBaseKey # Only true for NTLMv2
1753 if chall_tok.NegotiateFlags.NEGOTIATE_KEY_EXCH:
1754 ExportedSessionKey = os.urandom(16)
1755 tok.EncryptedRandomSessionKey = RC4K(
1756 KeyExchangeKey,
1757 ExportedSessionKey,
1758 )
1759 else:
1760 ExportedSessionKey = KeyExchangeKey
1762 # 4.5 Compute the MIC
1763 if self.USE_MIC:
1764 tok.compute_mic(ExportedSessionKey, Context.neg_tok, chall_tok)
1766 # 5. Perform key computations
1767 Context.ExportedSessionKey = ExportedSessionKey
1768 # [MS-SMB] 3.2.5.3
1769 Context.SessionKey = Context.ExportedSessionKey
1770 # Compute NTLM keys
1771 Context.SendSignKey = SIGNKEY(
1772 tok.NegotiateFlags, ExportedSessionKey, "Client"
1773 )
1774 Context.SendSealKey = SEALKEY(
1775 tok.NegotiateFlags, ExportedSessionKey, "Client"
1776 )
1777 Context.SendSealHandle = RC4Init(Context.SendSealKey)
1778 Context.RecvSignKey = SIGNKEY(
1779 tok.NegotiateFlags, ExportedSessionKey, "Server"
1780 )
1781 Context.RecvSealKey = SEALKEY(
1782 tok.NegotiateFlags, ExportedSessionKey, "Server"
1783 )
1784 Context.RecvSealHandle = RC4Init(Context.RecvSealKey)
1786 # Update the state
1787 Context.state = self.STATE.CLI_SENT_AUTH
1789 return Context, tok, GSS_S_COMPLETE
1790 elif Context.state == self.STATE.CLI_SENT_AUTH:
1791 if input_token:
1792 # what is that?
1793 status = GSS_S_DEFECTIVE_TOKEN
1794 else:
1795 status = GSS_S_COMPLETE
1796 return Context, None, status
1797 else:
1798 raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state))
1800 def GSS_Accept_sec_context(
1801 self,
1802 Context: CONTEXT,
1803 input_token=None,
1804 req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS,
1805 chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS,
1806 ):
1807 if Context is None:
1808 Context = self.CONTEXT(IsAcceptor=True, req_flags=req_flags)
1810 if Context.state == self.STATE.INIT:
1811 # Server: challenge (input_token=negotiate)
1812 nego_tok = input_token
1813 if not nego_tok or NTLM_NEGOTIATE not in nego_tok:
1814 log_runtime.debug("NTLMSSP: Unexpected token. Expected NTLM Negotiate")
1815 return Context, None, GSS_S_DEFECTIVE_TOKEN
1817 # Build the challenge token
1818 currentTime = (time.time() + 11644473600) * 1e7
1819 tok = NTLM_CHALLENGE(
1820 VARIANT=self.VARIANT,
1821 ServerChallenge=self.SERVER_CHALLENGE or os.urandom(8),
1822 NegotiateFlags="+".join(
1823 [
1824 "NEGOTIATE_UNICODE",
1825 "REQUEST_TARGET",
1826 "NEGOTIATE_NTLM",
1827 "NEGOTIATE_ALWAYS_SIGN",
1828 "NEGOTIATE_EXTENDED_SESSIONSECURITY",
1829 "NEGOTIATE_TARGET_INFO",
1830 "TARGET_TYPE_DOMAIN",
1831 "NEGOTIATE_128",
1832 "NEGOTIATE_KEY_EXCH",
1833 "NEGOTIATE_56",
1834 ]
1835 + (
1836 ["NEGOTIATE_VERSION"]
1837 if self.VARIANT >= NTLM_VARIANT.XP_OR_2003
1838 else []
1839 )
1840 + (
1841 ["NEGOTIATE_SIGN"]
1842 if nego_tok.NegotiateFlags.NEGOTIATE_SIGN
1843 else []
1844 )
1845 + (
1846 ["NEGOTIATE_SEAL"]
1847 if nego_tok.NegotiateFlags.NEGOTIATE_SEAL
1848 else []
1849 )
1850 ),
1851 ProductMajorVersion=10,
1852 ProductMinorVersion=0,
1853 Payload=[
1854 ("TargetName", ""),
1855 (
1856 "TargetInfo",
1857 [
1858 # MsvAvNbComputerName
1859 AV_PAIR(AvId=1, Value=self.COMPUTER_NB_NAME),
1860 # MsvAvNbDomainName
1861 AV_PAIR(AvId=2, Value=self.DOMAIN_NB_NAME),
1862 # MsvAvDnsComputerName
1863 AV_PAIR(AvId=3, Value=self.COMPUTER_FQDN),
1864 # MsvAvDnsDomainName
1865 AV_PAIR(AvId=4, Value=self.DOMAIN_FQDN),
1866 # MsvAvDnsTreeName
1867 AV_PAIR(AvId=5, Value=self.DOMAIN_FQDN),
1868 # MsvAvTimestamp
1869 AV_PAIR(AvId=7, Value=currentTime),
1870 # MsvAvEOL
1871 AV_PAIR(AvId=0),
1872 ],
1873 ),
1874 ],
1875 )
1876 if self.NTLM_VALUES:
1877 # Update that token with the customs one
1878 for key in [
1879 "ServerChallenge",
1880 "NegotiateFlags",
1881 "ProductMajorVersion",
1882 "ProductMinorVersion",
1883 "TargetName",
1884 ]:
1885 if key in self.NTLM_VALUES:
1886 setattr(tok, key, self.NTLM_VALUES[key])
1887 avpairs = {x.AvId: x.Value for x in tok.TargetInfo}
1888 tok.TargetInfo = [
1889 AV_PAIR(AvId=i, Value=self.NTLM_VALUES.get(x, avpairs[i]))
1890 for (i, x) in [
1891 (2, "NetbiosDomainName"),
1892 (1, "NetbiosComputerName"),
1893 (4, "DnsDomainName"),
1894 (3, "DnsComputerName"),
1895 (5, "DnsTreeName"),
1896 (6, "Flags"),
1897 (7, "Timestamp"),
1898 (0, None),
1899 ]
1900 if ((x in self.NTLM_VALUES) or (i in avpairs))
1901 and self.NTLM_VALUES.get(x, True) is not None
1902 ]
1904 # Store for next step
1905 Context.chall_tok = tok
1907 # Update the state
1908 Context.state = self.STATE.SRV_SENT_CHAL
1910 return Context, tok, GSS_S_CONTINUE_NEEDED
1911 elif Context.state == self.STATE.SRV_SENT_CHAL:
1912 # server: OK or challenge again (input_token=auth)
1913 auth_tok = input_token
1915 if not auth_tok or NTLM_AUTHENTICATE_V2 not in auth_tok:
1916 log_runtime.debug(
1917 "NTLMSSP: Unexpected token. Expected NTLM Authenticate v2"
1918 )
1919 return Context, None, GSS_S_DEFECTIVE_TOKEN
1921 if self.DO_NOT_CHECK_LOGIN:
1922 # Just trust me bro. Typically used in "guest" mode.
1923 return Context, None, GSS_S_COMPLETE
1925 # Compute the session key
1926 SessionBaseKey = self._getSessionBaseKey(Context, auth_tok)
1927 if SessionBaseKey:
1928 # [MS-NLMP] sect 3.2.5.1.2
1929 KeyExchangeKey = SessionBaseKey # Only true for NTLMv2
1930 if auth_tok.NegotiateFlags.NEGOTIATE_KEY_EXCH:
1931 try:
1932 EncryptedRandomSessionKey = auth_tok.EncryptedRandomSessionKey
1933 except AttributeError:
1934 # No EncryptedRandomSessionKey. libcurl for instance
1935 # hmm. this looks bad
1936 EncryptedRandomSessionKey = b"\x00" * 16
1937 ExportedSessionKey = RC4K(KeyExchangeKey, EncryptedRandomSessionKey)
1938 else:
1939 ExportedSessionKey = KeyExchangeKey
1940 Context.ExportedSessionKey = ExportedSessionKey
1941 # [MS-SMB] 3.2.5.3
1942 Context.SessionKey = Context.ExportedSessionKey
1944 # Check the timestamp
1945 try:
1946 ClientTimestamp = auth_tok.NtChallengeResponse.getAv(0x0007).Value
1947 ClientTime = (ClientTimestamp / 1e7) - 11644473600
1949 if abs(ClientTime - time.time()) >= NTLMSSP.NTLM_MaxLifetime:
1950 log_runtime.warning(
1951 "Server and Client times are off by more than 36h !"
1952 )
1953 # We could error here, but we don't.
1954 except IndexError:
1955 pass
1957 # Check the channel bindings
1958 if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS:
1959 try:
1960 Bnd = auth_tok.NtChallengeResponse.getAv(0x000A).Value
1961 if Bnd != chan_bindings.digestMD5():
1962 # Bad channel bindings
1963 return Context, None, GSS_S_BAD_BINDINGS
1964 except IndexError:
1965 if GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS not in req_flags:
1966 # Uhoh, we required channel bindings
1967 return Context, None, GSS_S_BAD_BINDINGS
1969 if Context.SessionKey:
1970 # Compute NTLM keys
1971 Context.SendSignKey = SIGNKEY(
1972 auth_tok.NegotiateFlags, ExportedSessionKey, "Server"
1973 )
1974 Context.SendSealKey = SEALKEY(
1975 auth_tok.NegotiateFlags, ExportedSessionKey, "Server"
1976 )
1977 Context.SendSealHandle = RC4Init(Context.SendSealKey)
1978 Context.RecvSignKey = SIGNKEY(
1979 auth_tok.NegotiateFlags, ExportedSessionKey, "Client"
1980 )
1981 Context.RecvSealKey = SEALKEY(
1982 auth_tok.NegotiateFlags, ExportedSessionKey, "Client"
1983 )
1984 Context.RecvSealHandle = RC4Init(Context.RecvSealKey)
1986 # Check the NTProofStr
1987 if self._checkLogin(Context, auth_tok):
1988 # Set negotiated flags
1989 if auth_tok.NegotiateFlags.NEGOTIATE_SIGN:
1990 Context.flags |= GSS_C_FLAGS.GSS_C_INTEG_FLAG
1991 if auth_tok.NegotiateFlags.NEGOTIATE_SEAL:
1992 Context.flags |= GSS_C_FLAGS.GSS_C_CONF_FLAG
1993 return Context, None, GSS_S_COMPLETE
1995 # Bad NTProofStr or unknown user
1996 Context.SessionKey = None
1997 Context.state = self.STATE.INIT
1998 return Context, None, GSS_S_DEFECTIVE_CREDENTIAL
1999 else:
2000 raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state))
2002 def MaximumSignatureLength(self, Context: CONTEXT):
2003 """
2004 Returns the Maximum Signature length.
2006 This will be used in auth_len in DceRpc5, and is necessary for
2007 PFC_SUPPORT_HEADER_SIGN to work properly.
2008 """
2009 return 16 # len(NTLMSSP_MESSAGE_SIGNATURE())
2011 def GSS_Passive(self, Context: CONTEXT, token=None, req_flags=None):
2012 if Context is None:
2013 Context = self.CONTEXT(True)
2014 Context.passive = True
2016 # We capture the Negotiate, Challenge, then call the server's auth handling
2017 # and discard the output.
2019 if Context.state == self.STATE.INIT:
2020 if not token or NTLM_NEGOTIATE not in token:
2021 log_runtime.warning("NTLMSSP: Expected NTLM Negotiate")
2022 return None, GSS_S_DEFECTIVE_TOKEN
2023 Context.neg_tok = token
2024 Context.state = self.STATE.CLI_SENT_NEGO
2025 return Context, GSS_S_CONTINUE_NEEDED
2026 elif Context.state == self.STATE.CLI_SENT_NEGO:
2027 if not token or NTLM_CHALLENGE not in token:
2028 log_runtime.warning("NTLMSSP: Expected NTLM Challenge")
2029 return None, GSS_S_DEFECTIVE_TOKEN
2030 Context.chall_tok = token
2031 Context.state = self.STATE.SRV_SENT_CHAL
2032 return Context, GSS_S_CONTINUE_NEEDED
2033 elif Context.state == self.STATE.SRV_SENT_CHAL:
2034 if not token or NTLM_AUTHENTICATE_V2 not in token:
2035 log_runtime.warning("NTLMSSP: Expected NTLM Authenticate")
2036 return None, GSS_S_DEFECTIVE_TOKEN
2037 Context, _, status = self.GSS_Accept_sec_context(Context, token)
2038 if status != GSS_S_COMPLETE:
2039 log_runtime.info("NTLMSSP: auth failed.")
2040 Context.state = self.STATE.INIT
2041 return Context, status
2042 else:
2043 raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state))
2045 def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False):
2046 if Context.IsAcceptor is not IsAcceptor:
2047 return
2048 # Swap everything
2049 Context.SendSignKey, Context.RecvSignKey = (
2050 Context.RecvSignKey,
2051 Context.SendSignKey,
2052 )
2053 Context.SendSealKey, Context.RecvSealKey = (
2054 Context.RecvSealKey,
2055 Context.SendSealKey,
2056 )
2057 Context.SendSealHandle, Context.RecvSealHandle = (
2058 Context.RecvSealHandle,
2059 Context.SendSealHandle,
2060 )
2061 Context.SendSeqNum, Context.RecvSeqNum = Context.RecvSeqNum, Context.SendSeqNum
2062 Context.IsAcceptor = not Context.IsAcceptor
2064 def _getSessionBaseKey(self, Context, auth_tok):
2065 """
2066 Function that returns the SessionBaseKey from the ntlm Authenticate.
2067 """
2068 try:
2069 # Windows usernames are case insensitive
2070 username = auth_tok.UserName.upper()
2071 except AttributeError:
2072 username = None
2073 try:
2074 domain = auth_tok.DomainName
2075 except AttributeError:
2076 domain = ""
2077 if self.IDENTITIES and username in self.IDENTITIES:
2078 ResponseKeyNT = NTOWFv2(
2079 None,
2080 username,
2081 domain,
2082 HashNt=self.IDENTITIES[username],
2083 )
2084 return NTLMv2_ComputeSessionBaseKey(
2085 ResponseKeyNT,
2086 auth_tok.NtChallengeResponse.NTProofStr,
2087 )
2088 elif self.IDENTITIES:
2089 log_runtime.debug("NTLMSSP: Bad credentials for %s" % username)
2090 return None
2092 def _checkLogin(self, Context, auth_tok):
2093 """
2094 Function that checks the validity of an authentication.
2096 Overwrite and return True to bypass.
2097 """
2098 try:
2099 # Windows usernames are case insensitive
2100 username = auth_tok.UserName.upper()
2101 except AttributeError:
2102 username = None
2103 try:
2104 domain = auth_tok.DomainName
2105 except AttributeError:
2106 domain = ""
2107 if username in self.IDENTITIES:
2108 ResponseKeyNT = NTOWFv2(
2109 None,
2110 username,
2111 domain,
2112 HashNt=self.IDENTITIES[username],
2113 )
2114 NTProofStr = auth_tok.NtChallengeResponse.computeNTProofStr(
2115 ResponseKeyNT,
2116 Context.chall_tok.ServerChallenge,
2117 )
2118 if NTProofStr == auth_tok.NtChallengeResponse.NTProofStr:
2119 return True
2120 return False
2123class NTLMSSP_DOMAIN(NTLMSSP):
2124 """
2125 A variant of the NTLMSSP to be used in server mode that gets the session
2126 keys from the domain using a Netlogon channel.
2128 This has the same arguments as NTLMSSP, but supports the following in server
2129 mode:
2131 :param UPN: the UPN of the machine account to login for Netlogon.
2132 :param HASHNT: the HASHNT of the machine account (use Netlogon secure channel).
2133 :param ssp: a KerberosSSP to use (use Kerberos secure channel).
2134 :param PASSWORD: the PASSWORD of the machine account to use for Netlogon.
2135 :param DC_IP: (optional) specify the IP of the DC.
2137 Netlogon example::
2139 >>> mySSP = NTLMSSP_DOMAIN(
2140 ... UPN="Server1@domain.local",
2141 ... HASHNT=bytes.fromhex("8846f7eaee8fb117ad06bdd830b7586c"),
2142 ... )
2144 Kerberos example::
2146 >>> mySSP = NTLMSSP_DOMAIN(
2147 ... UPN="Server1@domain.local",
2148 ... KEY=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96,
2149 ... key=bytes.fromhex(
2150 ... "85abb9b61dc2fa49d4cc04317bbd108f8f79df28"
2151 ... "239155ed7b144c5d2ebcf016"
2152 ... )
2153 ... ),
2154 ... )
2155 """
2157 def __init__(self, UPN=None, *args, timeout=3, ssp=None, **kwargs):
2158 from scapy.layers.kerberos import KerberosSSP
2160 # Either PASSWORD or HASHNT or ssp
2161 if (
2162 "HASHNT" not in kwargs
2163 and "PASSWORD" not in kwargs
2164 and "KEY" not in kwargs
2165 and ssp is None
2166 ):
2167 raise ValueError(
2168 "Must specify either 'HASHNT', 'PASSWORD' or "
2169 "provide a ssp=KerberosSSP()"
2170 )
2171 elif ssp is not None and not isinstance(ssp, KerberosSSP):
2172 raise ValueError("'ssp' can only be None or a KerberosSSP !")
2174 self.KEY = kwargs.pop("KEY", None)
2175 self.PASSWORD = kwargs.get("PASSWORD", None)
2177 # UPN is mandatory
2178 if UPN is None and ssp is not None and ssp.UPN:
2179 UPN = ssp.UPN
2180 elif UPN is None:
2181 raise ValueError("Must specify a 'UPN' !")
2182 kwargs["UPN"] = UPN
2184 # Call parent
2185 super(NTLMSSP_DOMAIN, self).__init__(
2186 *args,
2187 **kwargs,
2188 )
2190 # Treat specific parameters
2191 self.DC_FQDN = kwargs.pop("DC_FQDN", None)
2192 if self.DC_FQDN is None:
2193 # Get DC_FQDN from dclocator
2194 from scapy.layers.ldap import dclocator
2196 dc = dclocator(
2197 self.DOMAIN_FQDN,
2198 timeout=timeout,
2199 debug=kwargs.get("debug", 0),
2200 )
2201 self.DC_FQDN = dc.samlogon.DnsHostName.decode().rstrip(".")
2203 # If logging in via Kerberos
2204 self.ssp = ssp
2206 def _getSessionBaseKey(self, Context, ntlm):
2207 """
2208 Return the Session Key by asking the DC.
2209 """
2210 # No user / no domain: skip.
2211 if not ntlm.UserNameLen or not ntlm.DomainNameLen:
2212 return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm)
2214 # Import RPC stuff
2215 from scapy.layers.dcerpc import NDRUnion
2216 from scapy.layers.msrpce.msnrpc import (
2217 NETLOGON_SECURE_CHANNEL_METHOD,
2218 NetlogonClient,
2219 )
2220 from scapy.layers.msrpce.raw.ms_nrpc import (
2221 NETLOGON_LOGON_IDENTITY_INFO,
2222 NetrLogonSamLogonWithFlags_Request,
2223 PNETLOGON_AUTHENTICATOR,
2224 PNETLOGON_NETWORK_INFO,
2225 STRING,
2226 UNICODE_STRING,
2227 )
2229 # Create NetlogonClient with PRIVACY
2230 client = NetlogonClient()
2231 client.connect(self.DC_FQDN)
2233 # Establish the Netlogon secure channel (this will bind)
2234 try:
2235 if self.ssp is None and self.KEY is None:
2236 # Login via classic NetlogonSSP
2237 client.establish_secure_channel(
2238 mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3,
2239 UPN=f"{self.COMPUTER_NB_NAME}@{self.DOMAIN_NB_NAME}",
2240 DC_FQDN=self.DC_FQDN,
2241 HASHNT=self.HASHNT,
2242 )
2243 else:
2244 # Login via KerberosSSP (Windows 2025)
2245 client.establish_secure_channel(
2246 mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticateKerberos,
2247 UPN=self.UPN,
2248 DC_FQDN=self.DC_FQDN,
2249 PASSWORD=self.PASSWORD,
2250 KEY=self.KEY,
2251 ssp=self.ssp,
2252 )
2253 except ValueError:
2254 log_runtime.warning(
2255 "Couldn't establish the Netlogon secure channel. "
2256 "Check the credentials for '%s' !" % self.COMPUTER_NB_NAME
2257 )
2258 return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm)
2260 # Request validation of the NTLM request
2261 req = NetrLogonSamLogonWithFlags_Request(
2262 LogonServer="",
2263 ComputerName=self.COMPUTER_NB_NAME,
2264 Authenticator=client.create_authenticator(),
2265 ReturnAuthenticator=PNETLOGON_AUTHENTICATOR(),
2266 LogonLevel=6, # NetlogonNetworkTransitiveInformation
2267 LogonInformation=NDRUnion(
2268 tag=6,
2269 value=PNETLOGON_NETWORK_INFO(
2270 Identity=NETLOGON_LOGON_IDENTITY_INFO(
2271 LogonDomainName=UNICODE_STRING(
2272 Buffer=ntlm.DomainName,
2273 ),
2274 ParameterControl=0x00002AE0,
2275 UserName=UNICODE_STRING(
2276 Buffer=ntlm.UserName,
2277 ),
2278 Workstation=UNICODE_STRING(
2279 Buffer=ntlm.Workstation,
2280 ),
2281 ),
2282 LmChallenge=Context.chall_tok.ServerChallenge,
2283 NtChallengeResponse=STRING(
2284 Buffer=bytes(ntlm.NtChallengeResponse),
2285 ),
2286 LmChallengeResponse=STRING(
2287 Buffer=bytes(ntlm.LmChallengeResponse),
2288 ),
2289 ),
2290 ),
2291 ValidationLevel=6,
2292 ExtraFlags=0,
2293 ndr64=client.ndr64,
2294 )
2296 # Get response
2297 resp = client.sr1_req(req)
2298 if resp and resp.status == 0:
2299 # Success
2301 # Validate DC authenticator
2302 client.validate_authenticator(resp.ReturnAuthenticator.value)
2304 # Get and return the SessionKey
2305 UserSessionKey = resp.ValidationInformation.value.value.UserSessionKey
2306 return bytes(UserSessionKey)
2307 else:
2308 # Failed
2309 return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm)
2311 def _checkLogin(self, Context, auth_tok):
2312 # Always OK if we got the session key
2313 return True