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