• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5
6"""
7NTLM
8
9This is documented in [MS-NLMP]
10
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"""
15
16import copy
17import time
18import os
19import struct
20
21from enum import IntEnum
22
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
61
62from scapy.layers.gssapi import (
63    GSS_C_FLAGS,
64    GSS_S_COMPLETE,
65    GSS_S_CONTINUE_NEEDED,
66    GSS_S_DEFECTIVE_CREDENTIAL,
67    GSS_S_DEFECTIVE_TOKEN,
68    SSP,
69    _GSSAPI_OIDS,
70    _GSSAPI_SIGNATURE_OIDS,
71)
72
73# Typing imports
74from typing import (
75    Any,
76    Callable,
77    List,
78    Optional,
79    Tuple,
80    Union,
81)
82
83# Crypto imports
84
85from scapy.layers.tls.crypto.hash import Hash_MD4, Hash_MD5
86from scapy.layers.tls.crypto.h_mac import Hmac_MD5
87
88##########
89# Fields #
90##########
91
92
93class _NTLMPayloadField(_StrField[List[Tuple[str, Any]]]):
94    """Special field used to dissect NTLM payloads.
95    This isn't trivial because the offsets are variable."""
96
97    __slots__ = [
98        "fields",
99        "fields_map",
100        "offset",
101        "length_from",
102        "force_order",
103        "offset_name",
104    ]
105    islist = True
106
107    def __init__(
108        self,
109        name,  # type: str
110        offset,  # type: Union[int, Callable[[Packet], int]]
111        fields,  # type: List[Field[Any, Any]]
112        length_from=None,  # type: Optional[Callable[[Packet], int]]
113        force_order=None,  # type: Optional[List[str]]
114        offset_name="BufferOffset",  # type: str
115    ):
116        # type: (...) -> None
117        self.offset = offset
118        self.fields = fields
119        self.fields_map = {field.name: field for field in fields}
120        self.length_from = length_from
121        self.force_order = force_order  # whether the order of fields is fixed
122        self.offset_name = offset_name
123        super(_NTLMPayloadField, self).__init__(
124            name,
125            [
126                (field.name, field.default)
127                for field in fields
128                if field.default is not None
129            ],
130        )
131
132    def _on_payload(self, pkt, x, func):
133        # type: (Optional[Packet], bytes, str) -> List[Tuple[str, Any]]
134        if not pkt or not x:
135            return []
136        results = []
137        for field_name, value in x:
138            if field_name not in self.fields_map:
139                continue
140            if not isinstance(
141                self.fields_map[field_name], PacketListField
142            ) and not isinstance(value, Packet):
143                value = getattr(self.fields_map[field_name], func)(pkt, value)
144            results.append((field_name, value))
145        return results
146
147    def i2h(self, pkt, x):
148        # type: (Optional[Packet], bytes) -> List[Tuple[str, str]]
149        return self._on_payload(pkt, x, "i2h")
150
151    def h2i(self, pkt, x):
152        # type: (Optional[Packet], bytes) -> List[Tuple[str, str]]
153        return self._on_payload(pkt, x, "h2i")
154
155    def i2repr(self, pkt, x):
156        # type: (Optional[Packet], bytes) -> str
157        return repr(self._on_payload(pkt, x, "i2repr"))
158
159    def _o_pkt(self, pkt):
160        # type: (Optional[Packet]) -> int
161        if callable(self.offset):
162            return self.offset(pkt)
163        return self.offset
164
165    def addfield(self, pkt, s, val):
166        # type: (Optional[Packet], bytes, Optional[List[Tuple[str, str]]]) -> bytes
167        # Create string buffer
168        buf = StringBuffer()
169        buf.append(s, 1)
170        # Calc relative offset
171        r_off = self._o_pkt(pkt) - len(s)
172        if self.force_order:
173            val.sort(key=lambda x: self.force_order.index(x[0]))
174        for field_name, value in val:
175            if field_name not in self.fields_map:
176                continue
177            field = self.fields_map[field_name]
178            offset = pkt.getfieldval(field_name + self.offset_name)
179            if offset is None:
180                # No offset specified: calc
181                offset = len(buf)
182            else:
183                # Calc relative offset
184                offset -= r_off
185                pad = offset + 1 - len(buf)
186                # Add padding if necessary
187                if pad > 0:
188                    buf.append(pad * b"\x00", len(buf))
189            buf.append(field.addfield(pkt, bytes(buf), value)[len(buf) :], offset + 1)
190        return bytes(buf)
191
192    def getfield(self, pkt, s):
193        # type: (Packet, bytes) -> Tuple[bytes, List[Tuple[str, str]]]
194        if self.length_from is None:
195            ret, remain = b"", s
196        else:
197            len_pkt = self.length_from(pkt)
198            ret, remain = s[len_pkt:], s[:len_pkt]
199        if not pkt or not remain:
200            return s, []
201        results = []
202        max_offset = 0
203        o_pkt = self._o_pkt(pkt)
204        offsets = [
205            pkt.getfieldval(x.name + self.offset_name) - o_pkt for x in self.fields
206        ]
207        for i, field in enumerate(self.fields):
208            offset = offsets[i]
209            try:
210                length = pkt.getfieldval(field.name + "Len")
211            except AttributeError:
212                length = len(remain) - offset
213                # length can't be greater than the difference with the next offset
214                try:
215                    length = min(length, min(x - offset for x in offsets if x > offset))
216                except ValueError:
217                    pass
218            if offset < 0:
219                continue
220            max_offset = max(offset + length, max_offset)
221            if remain[offset : offset + length]:
222                results.append(
223                    (
224                        offset,
225                        field.name,
226                        field.getfield(pkt, remain[offset : offset + length])[1],
227                    )
228                )
229        ret += remain[max_offset:]
230        results.sort(key=lambda x: x[0])
231        return ret, [x[1:] for x in results]
232
233
234class _NTLMPayloadPacket(Packet):
235    _NTLM_PAYLOAD_FIELD_NAME = "Payload"
236
237    def __init__(
238        self,
239        _pkt=b"",  # type: Union[bytes, bytearray]
240        post_transform=None,  # type: Any
241        _internal=0,  # type: int
242        _underlayer=None,  # type: Optional[Packet]
243        _parent=None,  # type: Optional[Packet]
244        **fields,  # type: Any
245    ):
246        # pop unknown fields. We can't process them until the packet is initialized
247        unknown = {
248            k: fields.pop(k)
249            for k in list(fields)
250            if not any(k == f.name for f in self.fields_desc)
251        }
252        super(_NTLMPayloadPacket, self).__init__(
253            _pkt=_pkt,
254            post_transform=post_transform,
255            _internal=_internal,
256            _underlayer=_underlayer,
257            _parent=_parent,
258            **fields,
259        )
260        # check unknown fields for implicit ones
261        local_fields = next(
262            [y.name for y in x.fields]
263            for x in self.fields_desc
264            if x.name == self._NTLM_PAYLOAD_FIELD_NAME
265        )
266        implicit_fields = {k: v for k, v in unknown.items() if k in local_fields}
267        for k, value in implicit_fields.items():
268            self.setfieldval(k, value)
269
270    def getfieldval(self, attr):
271        # Ease compatibility with _NTLMPayloadField
272        try:
273            return super(_NTLMPayloadPacket, self).getfieldval(attr)
274        except AttributeError:
275            try:
276                return next(
277                    x[1]
278                    for x in super(_NTLMPayloadPacket, self).getfieldval(
279                        self._NTLM_PAYLOAD_FIELD_NAME
280                    )
281                    if x[0] == attr
282                )
283            except StopIteration:
284                raise AttributeError(attr)
285
286    def getfield_and_val(self, attr):
287        # Ease compatibility with _NTLMPayloadField
288        try:
289            return super(_NTLMPayloadPacket, self).getfield_and_val(attr)
290        except ValueError:
291            PayFields = self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map
292            try:
293                return (
294                    PayFields[attr],
295                    PayFields[attr].h2i(  # cancel out the i2h.. it's dumb i know
296                        self,
297                        next(
298                            x[1]
299                            for x in super(_NTLMPayloadPacket, self).__getattr__(
300                                self._NTLM_PAYLOAD_FIELD_NAME
301                            )
302                            if x[0] == attr
303                        ),
304                    ),
305                )
306            except (StopIteration, KeyError):
307                raise ValueError(attr)
308
309    def setfieldval(self, attr, val):
310        # Ease compatibility with _NTLMPayloadField
311        try:
312            return super(_NTLMPayloadPacket, self).setfieldval(attr, val)
313        except AttributeError:
314            Payload = super(_NTLMPayloadPacket, self).__getattr__(
315                self._NTLM_PAYLOAD_FIELD_NAME
316            )
317            if attr not in self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map:
318                raise AttributeError(attr)
319            try:
320                Payload.pop(
321                    next(
322                        i
323                        for i, x in enumerate(
324                            super(_NTLMPayloadPacket, self).__getattr__(
325                                self._NTLM_PAYLOAD_FIELD_NAME
326                            )
327                        )
328                        if x[0] == attr
329                    )
330                )
331            except StopIteration:
332                pass
333            Payload.append([attr, val])
334            super(_NTLMPayloadPacket, self).setfieldval(
335                self._NTLM_PAYLOAD_FIELD_NAME, Payload
336            )
337
338
339class _NTLM_ENUM(IntEnum):
340    LEN = 0x0001
341    MAXLEN = 0x0002
342    OFFSET = 0x0004
343    COUNT = 0x0008
344    PAD8 = 0x1000
345
346
347_NTLM_CONFIG = [
348    ("Len", _NTLM_ENUM.LEN),
349    ("MaxLen", _NTLM_ENUM.MAXLEN),
350    ("BufferOffset", _NTLM_ENUM.OFFSET),
351]
352
353
354def _NTLM_post_build(self, p, pay_offset, fields, config=_NTLM_CONFIG):
355    """Util function to build the offset and populate the lengths"""
356    for field_name, value in self.fields[self._NTLM_PAYLOAD_FIELD_NAME]:
357        fld = self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map[field_name]
358        length = fld.i2len(self, value)
359        count = fld.i2count(self, value)
360        offset = fields[field_name]
361        i = 0
362        r = lambda y: {2: "H", 4: "I", 8: "Q"}[y]
363        for fname, ftype in config:
364            if isinstance(ftype, dict):
365                ftype = ftype[field_name]
366            if ftype & _NTLM_ENUM.LEN:
367                fval = length
368            elif ftype & _NTLM_ENUM.OFFSET:
369                fval = pay_offset
370            elif ftype & _NTLM_ENUM.MAXLEN:
371                fval = length
372            elif ftype & _NTLM_ENUM.COUNT:
373                fval = count
374            else:
375                raise ValueError
376            if ftype & _NTLM_ENUM.PAD8:
377                fval += (-fval) % 8
378            sz = self.get_field(field_name + fname).sz
379            if self.getfieldval(field_name + fname) is None:
380                p = (
381                    p[: offset + i]
382                    + struct.pack("<%s" % r(sz), fval)
383                    + p[offset + i + sz :]
384                )
385            i += sz
386        pay_offset += length
387    return p
388
389
390##############
391# Structures #
392##############
393
394
395# Sect 2.2
396
397
398class NTLM_Header(Packet):
399    name = "NTLM Header"
400    fields_desc = [
401        StrFixedLenField("Signature", b"NTLMSSP\0", length=8),
402        LEIntEnumField(
403            "MessageType",
404            3,
405            {1: "NEGOTIATE_MESSAGE", 2: "CHALLENGE_MESSAGE", 3: "AUTHENTICATE_MESSAGE"},
406        ),
407    ]
408
409    @classmethod
410    def dispatch_hook(cls, _pkt=None, *args, **kargs):
411        if _pkt and len(_pkt) >= 10:
412            MessageType = struct.unpack("<H", _pkt[8:10])[0]
413            if MessageType == 1:
414                return NTLM_NEGOTIATE
415            elif MessageType == 2:
416                return NTLM_CHALLENGE
417            elif MessageType == 3:
418                return NTLM_AUTHENTICATE_V2
419        return cls
420
421
422# Sect 2.2.2.5
423_negotiateFlags = [
424    "NEGOTIATE_UNICODE",  # A
425    "NEGOTIATE_OEM",  # B
426    "REQUEST_TARGET",  # C
427    "r10",
428    "NEGOTIATE_SIGN",  # D
429    "NEGOTIATE_SEAL",  # E
430    "NEGOTIATE_DATAGRAM",  # F
431    "NEGOTIATE_LM_KEY",  # G
432    "r9",
433    "NEGOTIATE_NTLM",  # H
434    "r8",
435    "J",
436    "NEGOTIATE_OEM_DOMAIN_SUPPLIED",  # K
437    "NEGOTIATE_OEM_WORKSTATION_SUPPLIED",  # L
438    "r7",
439    "NEGOTIATE_ALWAYS_SIGN",  # M
440    "TARGET_TYPE_DOMAIN",  # N
441    "TARGET_TYPE_SERVER",  # O
442    "r6",
443    "NEGOTIATE_EXTENDED_SESSIONSECURITY",  # P
444    "NEGOTIATE_IDENTIFY",  # Q
445    "r5",
446    "REQUEST_NON_NT_SESSION_KEY",  # R
447    "NEGOTIATE_TARGET_INFO",  # S
448    "r4",
449    "NEGOTIATE_VERSION",  # T
450    "r3",
451    "r2",
452    "r1",
453    "NEGOTIATE_128",  # U
454    "NEGOTIATE_KEY_EXCH",  # V
455    "NEGOTIATE_56",  # W
456]
457
458
459def _NTLMStrField(name, default):
460    return MultipleTypeField(
461        [
462            (
463                StrFieldUtf16(name, default),
464                lambda pkt: pkt.NegotiateFlags.NEGOTIATE_UNICODE,
465            )
466        ],
467        StrField(name, default),
468    )
469
470
471# Sect 2.2.2.10
472
473
474class _NTLM_Version(Packet):
475    fields_desc = [
476        ByteField("ProductMajorVersion", 0),
477        ByteField("ProductMinorVersion", 0),
478        LEShortField("ProductBuild", 0),
479        LEThreeBytesField("res_ver", 0),
480        ByteEnumField("NTLMRevisionCurrent", 0x0F, {0x0F: "v15"}),
481    ]
482
483
484# Sect 2.2.1.1
485
486
487class NTLM_NEGOTIATE(_NTLMPayloadPacket):
488    name = "NTLM Negotiate"
489    MessageType = 1
490    OFFSET = lambda pkt: (((pkt.DomainNameBufferOffset or 40) > 32) and 40 or 32)
491    fields_desc = (
492        [
493            NTLM_Header,
494            FlagsField("NegotiateFlags", 0, -32, _negotiateFlags),
495            # DomainNameFields
496            LEShortField("DomainNameLen", None),
497            LEShortField("DomainNameMaxLen", None),
498            LEIntField("DomainNameBufferOffset", None),
499            # WorkstationFields
500            LEShortField("WorkstationNameLen", None),
501            LEShortField("WorkstationNameMaxLen", None),
502            LEIntField("WorkstationNameBufferOffset", None),
503        ]
504        + [
505            # VERSION
506            ConditionalField(
507                # (not present on some old Windows versions. We use a heuristic)
508                x,
509                lambda pkt: (
510                    (
511                        40
512                        if pkt.DomainNameBufferOffset is None
513                        else pkt.DomainNameBufferOffset or len(pkt.original or b"")
514                    )
515                    > 32
516                )
517                or pkt.fields.get(x.name, b""),
518            )
519            for x in _NTLM_Version.fields_desc
520        ]
521        + [
522            # Payload
523            _NTLMPayloadField(
524                "Payload",
525                OFFSET,
526                [
527                    _NTLMStrField("DomainName", b""),
528                    _NTLMStrField("WorkstationName", b""),
529                ],
530            ),
531        ]
532    )
533
534    def post_build(self, pkt, pay):
535        # type: (bytes, bytes) -> bytes
536        return (
537            _NTLM_post_build(
538                self,
539                pkt,
540                self.OFFSET(),
541                {
542                    "DomainName": 16,
543                    "WorkstationName": 24,
544                },
545            )
546            + pay
547        )
548
549
550# Challenge
551
552
553class Single_Host_Data(Packet):
554    fields_desc = [
555        LEIntField("Size", 48),
556        LEIntField("Z4", 0),
557        XStrFixedLenField("CustomData", b"", length=8),
558        XStrFixedLenField("MachineID", b"", length=32),
559    ]
560
561    def default_payload_class(self, payload):
562        return conf.padding_layer
563
564
565class AV_PAIR(Packet):
566    name = "NTLM AV Pair"
567    fields_desc = [
568        LEShortEnumField(
569            "AvId",
570            0,
571            {
572                0x0000: "MsvAvEOL",
573                0x0001: "MsvAvNbComputerName",
574                0x0002: "MsvAvNbDomainName",
575                0x0003: "MsvAvDnsComputerName",
576                0x0004: "MsvAvDnsDomainName",
577                0x0005: "MsvAvDnsTreeName",
578                0x0006: "MsvAvFlags",
579                0x0007: "MsvAvTimestamp",
580                0x0008: "MsvAvSingleHost",
581                0x0009: "MsvAvTargetName",
582                0x000A: "MsvAvChannelBindings",
583            },
584        ),
585        FieldLenField("AvLen", None, length_of="Value", fmt="<H"),
586        MultipleTypeField(
587            [
588                (
589                    LEIntEnumField(
590                        "Value",
591                        1,
592                        {
593                            0x0001: "constrained",
594                            0x0002: "MIC integrity",
595                            0x0004: "SPN from untrusted source",
596                        },
597                    ),
598                    lambda pkt: pkt.AvId == 0x0006,
599                ),
600                (
601                    UTCTimeField(
602                        "Value",
603                        None,
604                        epoch=[1601, 1, 1, 0, 0, 0],
605                        custom_scaling=1e7,
606                        fmt="<Q",
607                    ),
608                    lambda pkt: pkt.AvId == 0x0007,
609                ),
610                (
611                    PacketField("Value", Single_Host_Data(), Single_Host_Data),
612                    lambda pkt: pkt.AvId == 0x0008,
613                ),
614                (
615                    XStrLenField("Value", b"", length_from=lambda pkt: pkt.AvLen),
616                    lambda pkt: pkt.AvId == 0x000A,
617                ),
618            ],
619            StrLenFieldUtf16("Value", b"", length_from=lambda pkt: pkt.AvLen),
620        ),
621    ]
622
623    def default_payload_class(self, payload):
624        return conf.padding_layer
625
626
627class NTLM_CHALLENGE(_NTLMPayloadPacket):
628    name = "NTLM Challenge"
629    MessageType = 2
630    OFFSET = lambda pkt: (((pkt.TargetInfoBufferOffset or 56) > 48) and 56 or 48)
631    fields_desc = (
632        [
633            NTLM_Header,
634            # TargetNameFields
635            LEShortField("TargetNameLen", None),
636            LEShortField("TargetNameMaxLen", None),
637            LEIntField("TargetNameBufferOffset", None),
638            #
639            FlagsField("NegotiateFlags", 0, -32, _negotiateFlags),
640            XStrFixedLenField("ServerChallenge", None, length=8),
641            XStrFixedLenField("Reserved", None, length=8),
642            # TargetInfoFields
643            LEShortField("TargetInfoLen", None),
644            LEShortField("TargetInfoMaxLen", None),
645            LEIntField("TargetInfoBufferOffset", None),
646        ]
647        + [
648            # VERSION
649            ConditionalField(
650                # (not present on some old Windows versions. We use a heuristic)
651                x,
652                lambda pkt: ((pkt.TargetInfoBufferOffset or 56) > 40)
653                or pkt.fields.get(x.name, b""),
654            )
655            for x in _NTLM_Version.fields_desc
656        ]
657        + [
658            # Payload
659            _NTLMPayloadField(
660                "Payload",
661                OFFSET,
662                [
663                    _NTLMStrField("TargetName", b""),
664                    PacketListField("TargetInfo", [AV_PAIR()], AV_PAIR),
665                ],
666            ),
667        ]
668    )
669
670    def getAv(self, AvId):
671        try:
672            return next(x for x in self.TargetInfo if x.AvId == AvId)
673        except (StopIteration, AttributeError):
674            raise IndexError
675
676    def post_build(self, pkt, pay):
677        # type: (bytes, bytes) -> bytes
678        return (
679            _NTLM_post_build(
680                self,
681                pkt,
682                self.OFFSET(),
683                {
684                    "TargetName": 12,
685                    "TargetInfo": 40,
686                },
687            )
688            + pay
689        )
690
691
692# Authenticate
693
694
695class LM_RESPONSE(Packet):
696    fields_desc = [
697        StrFixedLenField("Response", b"", length=24),
698    ]
699
700
701class LMv2_RESPONSE(Packet):
702    fields_desc = [
703        StrFixedLenField("Response", b"", length=16),
704        StrFixedLenField("ChallengeFromClient", b"", length=8),
705    ]
706
707
708class NTLM_RESPONSE(Packet):
709    fields_desc = [
710        StrFixedLenField("Response", b"", length=24),
711    ]
712
713
714class NTLMv2_CLIENT_CHALLENGE(Packet):
715    fields_desc = [
716        ByteField("RespType", 1),
717        ByteField("HiRespType", 1),
718        LEShortField("Reserved1", 0),
719        LEIntField("Reserved2", 0),
720        UTCTimeField(
721            "TimeStamp", None, fmt="<Q", epoch=[1601, 1, 1, 0, 0, 0], custom_scaling=1e7
722        ),
723        StrFixedLenField("ChallengeFromClient", b"12345678", length=8),
724        LEIntField("Reserved3", 0),
725        PacketListField("AvPairs", [AV_PAIR()], AV_PAIR),
726    ]
727
728    def getAv(self, AvId):
729        try:
730            return next(x for x in self.AvPairs if x.AvId == AvId)
731        except StopIteration:
732            raise IndexError
733
734
735class NTLMv2_RESPONSE(NTLMv2_CLIENT_CHALLENGE):
736    fields_desc = [
737        XStrFixedLenField("NTProofStr", b"", length=16),
738        NTLMv2_CLIENT_CHALLENGE,
739    ]
740
741    def computeNTProofStr(self, ResponseKeyNT, ServerChallenge):
742        """
743        Set temp to ConcatenationOf(Responserversion, HiResponserversion,
744            Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4))
745        Set NTProofStr to HMAC_MD5(ResponseKeyNT,
746            ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge,temp))
747
748        Remember ServerName = AvPairs
749        """
750        Responserversion = b"\x01"
751        HiResponserversion = b"\x01"
752
753        ServerName = b"".join(bytes(x) for x in self.AvPairs)
754        temp = b"".join(
755            [
756                Responserversion,
757                HiResponserversion,
758                b"\x00" * 6,
759                struct.pack("<Q", self.TimeStamp),
760                self.ChallengeFromClient,
761                b"\x00" * 4,
762                ServerName,
763                # Final Z(4) is the EOL AvPair
764            ]
765        )
766        return HMAC_MD5(ResponseKeyNT, ServerChallenge + temp)
767
768
769class NTLM_AUTHENTICATE(_NTLMPayloadPacket):
770    name = "NTLM Authenticate"
771    MessageType = 3
772    NTLM_VERSION = 1
773    OFFSET = lambda pkt: (
774        ((pkt.DomainNameBufferOffset or 88) <= 64)
775        and 64
776        or (((pkt.DomainNameBufferOffset or 88) > 72) and 88 or 72)
777    )
778    fields_desc = (
779        [
780            NTLM_Header,
781            # LmChallengeResponseFields
782            LEShortField("LmChallengeResponseLen", None),
783            LEShortField("LmChallengeResponseMaxLen", None),
784            LEIntField("LmChallengeResponseBufferOffset", None),
785            # NtChallengeResponseFields
786            LEShortField("NtChallengeResponseLen", None),
787            LEShortField("NtChallengeResponseMaxLen", None),
788            LEIntField("NtChallengeResponseBufferOffset", None),
789            # DomainNameFields
790            LEShortField("DomainNameLen", None),
791            LEShortField("DomainNameMaxLen", None),
792            LEIntField("DomainNameBufferOffset", None),
793            # UserNameFields
794            LEShortField("UserNameLen", None),
795            LEShortField("UserNameMaxLen", None),
796            LEIntField("UserNameBufferOffset", None),
797            # WorkstationFields
798            LEShortField("WorkstationLen", None),
799            LEShortField("WorkstationMaxLen", None),
800            LEIntField("WorkstationBufferOffset", None),
801            # EncryptedRandomSessionKeyFields
802            LEShortField("EncryptedRandomSessionKeyLen", None),
803            LEShortField("EncryptedRandomSessionKeyMaxLen", None),
804            LEIntField("EncryptedRandomSessionKeyBufferOffset", None),
805            # NegotiateFlags
806            FlagsField("NegotiateFlags", 0, -32, _negotiateFlags),
807            # VERSION
808        ]
809        + [
810            ConditionalField(
811                # (not present on some old Windows versions. We use a heuristic)
812                x,
813                lambda pkt: ((pkt.DomainNameBufferOffset or 88) > 64)
814                or pkt.fields.get(x.name, b""),
815            )
816            for x in _NTLM_Version.fields_desc
817        ]
818        + [
819            # MIC
820            ConditionalField(
821                # (not present on some old Windows versions. We use a heuristic)
822                XStrFixedLenField("MIC", b"", length=16),
823                lambda pkt: ((pkt.DomainNameBufferOffset or 88) > 72)
824                or pkt.fields.get("MIC", b""),
825            ),
826            # Payload
827            _NTLMPayloadField(
828                "Payload",
829                OFFSET,
830                [
831                    MultipleTypeField(
832                        [
833                            (
834                                PacketField(
835                                    "LmChallengeResponse",
836                                    LMv2_RESPONSE(),
837                                    LMv2_RESPONSE,
838                                ),
839                                lambda pkt: pkt.NTLM_VERSION == 2,
840                            )
841                        ],
842                        PacketField("LmChallengeResponse", LM_RESPONSE(), LM_RESPONSE),
843                    ),
844                    MultipleTypeField(
845                        [
846                            (
847                                PacketField(
848                                    "NtChallengeResponse",
849                                    NTLMv2_RESPONSE(),
850                                    NTLMv2_RESPONSE,
851                                ),
852                                lambda pkt: pkt.NTLM_VERSION == 2,
853                            )
854                        ],
855                        PacketField(
856                            "NtChallengeResponse", NTLM_RESPONSE(), NTLM_RESPONSE
857                        ),
858                    ),
859                    _NTLMStrField("DomainName", b""),
860                    _NTLMStrField("UserName", b""),
861                    _NTLMStrField("Workstation", b""),
862                    XStrField("EncryptedRandomSessionKey", b""),
863                ],
864            ),
865        ]
866    )
867
868    def post_build(self, pkt, pay):
869        # type: (bytes, bytes) -> bytes
870        return (
871            _NTLM_post_build(
872                self,
873                pkt,
874                self.OFFSET(),
875                {
876                    "LmChallengeResponse": 12,
877                    "NtChallengeResponse": 20,
878                    "DomainName": 28,
879                    "UserName": 36,
880                    "Workstation": 44,
881                    "EncryptedRandomSessionKey": 52,
882                },
883            )
884            + pay
885        )
886
887    def compute_mic(self, ExportedSessionKey, negotiate, challenge):
888        self.MIC = b"\x00" * 16
889        self.MIC = HMAC_MD5(
890            ExportedSessionKey, bytes(negotiate) + bytes(challenge) + bytes(self)
891        )
892
893
894class NTLM_AUTHENTICATE_V2(NTLM_AUTHENTICATE):
895    NTLM_VERSION = 2
896
897
898def HTTP_ntlm_negotiate(ntlm_negotiate):
899    """Create an HTTP NTLM negotiate packet from an NTLM_NEGOTIATE message"""
900    assert isinstance(ntlm_negotiate, NTLM_NEGOTIATE)
901    from scapy.layers.http import HTTP, HTTPRequest
902
903    return HTTP() / HTTPRequest(
904        Authorization=b"NTLM " + bytes_base64(bytes(ntlm_negotiate))
905    )
906
907
908# Experimental - Reversed stuff
909
910# This is the GSSAPI NegoEX Exchange metadata blob. This is not documented
911# but described as an "opaque blob": this was reversed and everything is a
912# placeholder.
913
914
915class NEGOEX_EXCHANGE_NTLM_ITEM(ASN1_Packet):
916    ASN1_codec = ASN1_Codecs.BER
917    ASN1_root = ASN1F_SEQUENCE(
918        ASN1F_SEQUENCE(
919            ASN1F_SEQUENCE(
920                ASN1F_OID("oid", ""),
921                ASN1F_PRINTABLE_STRING("token", ""),
922                explicit_tag=0x31,
923            ),
924            explicit_tag=0x80,
925        )
926    )
927
928
929class NEGOEX_EXCHANGE_NTLM(ASN1_Packet):
930    """
931    GSSAPI NegoEX Exchange metadata blob
932    This was reversed and may be meaningless
933    """
934
935    ASN1_codec = ASN1_Codecs.BER
936    ASN1_root = ASN1F_SEQUENCE(
937        ASN1F_SEQUENCE(
938            ASN1F_SEQUENCE_OF("items", [], NEGOEX_EXCHANGE_NTLM_ITEM), implicit_tag=0xA0
939        ),
940    )
941
942
943# Crypto - [MS-NLMP]
944
945
946def HMAC_MD5(key, data):
947    return Hmac_MD5(key=key).digest(data)
948
949
950def MD4le(x):
951    """
952    MD4 over a string encoded as utf-16le
953    """
954    return Hash_MD4().digest(x.encode("utf-16le"))
955
956
957def RC4Init(key):
958    """Alleged RC4"""
959    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
960
961    try:
962        # cryptography > 43.0
963        from cryptography.hazmat.decrepit.ciphers import (
964            algorithms as decrepit_algorithms,
965        )
966    except ImportError:
967        decrepit_algorithms = algorithms
968
969    algorithm = decrepit_algorithms.ARC4(key)
970    cipher = Cipher(algorithm, mode=None)
971    encryptor = cipher.encryptor()
972    return encryptor
973
974
975def RC4(handle, data):
976    """The RC4 Encryption Algorithm"""
977    return handle.update(data)
978
979
980def RC4K(key, data):
981    """Indicates the encryption of data item D with the key K using the
982    RC4 algorithm.
983    """
984    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
985
986    try:
987        # cryptography > 43.0
988        from cryptography.hazmat.decrepit.ciphers import (
989            algorithms as decrepit_algorithms,
990        )
991    except ImportError:
992        decrepit_algorithms = algorithms
993
994    algorithm = decrepit_algorithms.ARC4(key)
995    cipher = Cipher(algorithm, mode=None)
996    encryptor = cipher.encryptor()
997    return encryptor.update(data) + encryptor.finalize()
998
999
1000# sect 2.2.2.9 - With Extended Session Security
1001
1002
1003class NTLMSSP_MESSAGE_SIGNATURE(Packet):
1004    # [MS-RPCE] sect 2.2.2.9.1/2.2.2.9.2
1005    fields_desc = [
1006        LEIntField("Version", 0x00000001),
1007        XStrFixedLenField("Checksum", b"", length=8),
1008        LEIntField("SeqNum", 0x00000000),
1009    ]
1010
1011    def default_payload_class(self, payload):
1012        return conf.padding_layer
1013
1014
1015_GSSAPI_OIDS["1.3.6.1.4.1.311.2.2.10"] = NTLM_Header
1016_GSSAPI_SIGNATURE_OIDS["1.3.6.1.4.1.311.2.2.10"] = NTLMSSP_MESSAGE_SIGNATURE
1017
1018
1019# sect 3.3.2
1020
1021
1022def NTOWFv2(Passwd, User, UserDom, HashNt=None):
1023    """
1024    Computes the ResponseKeyNT (per [MS-NLMP] sect 3.3.2)
1025
1026    :param Passwd: the plain password
1027    :param User: the username
1028    :param UserDom: the domain name
1029    :param HashNt: (out of spec) if you have the HashNt, use this and set
1030                   Passwd to None
1031    """
1032    if HashNt is None:
1033        HashNt = MD4le(Passwd)
1034    return HMAC_MD5(HashNt, (User.upper() + UserDom).encode("utf-16le"))
1035
1036
1037def NTLMv2_ComputeSessionBaseKey(ResponseKeyNT, NTProofStr):
1038    return HMAC_MD5(ResponseKeyNT, NTProofStr)
1039
1040
1041# sect 3.4.4.2 - With Extended Session Security
1042
1043
1044def MAC(Handle, SigningKey, SeqNum, Message):
1045    chksum = HMAC_MD5(SigningKey, struct.pack("<i", SeqNum) + Message)[:8]
1046    if Handle:
1047        chksum = RC4(Handle, chksum)
1048    return NTLMSSP_MESSAGE_SIGNATURE(
1049        Version=0x00000001,
1050        Checksum=chksum,
1051        SeqNum=SeqNum,
1052    )
1053
1054
1055# sect 3.4.2
1056
1057
1058def SIGN(Handle, SigningKey, SeqNum, Message):
1059    # append? where is this used?!
1060    return Message + MAC(Handle, SigningKey, SeqNum, Message)
1061
1062
1063# sect 3.4.3
1064
1065
1066def SEAL(Handle, SigningKey, SeqNum, Message):
1067    """
1068    SEAL() according to [MS-NLMP]
1069    """
1070    # this is unused. Use GSS_WrapEx
1071    sealed_message = RC4(Handle, Message)
1072    signature = MAC(Handle, SigningKey, SeqNum, Message)
1073    return sealed_message, signature
1074
1075
1076def UNSEAL(Handle, SigningKey, SeqNum, Message):
1077    """
1078    UNSEAL() according to [MS-NLMP]
1079    """
1080    # this is unused. Use GSS_UnwrapEx
1081    unsealed_message = RC4(Handle, Message)
1082    signature = MAC(Handle, SigningKey, SeqNum, Message)
1083    return unsealed_message, signature
1084
1085
1086# sect 3.4.5.2
1087
1088
1089def SIGNKEY(NegFlg, ExportedSessionKey, Mode):
1090    if NegFlg.NEGOTIATE_EXTENDED_SESSIONSECURITY:
1091        if Mode == "Client":
1092            return Hash_MD5().digest(
1093                ExportedSessionKey
1094                + b"session key to client-to-server signing key magic constant\x00"
1095            )
1096        elif Mode == "Server":
1097            return Hash_MD5().digest(
1098                ExportedSessionKey
1099                + b"session key to server-to-client signing key magic constant\x00"
1100            )
1101        else:
1102            raise ValueError("Unknown Mode")
1103    else:
1104        return None
1105
1106
1107# sect 3.4.5.3
1108
1109
1110def SEALKEY(NegFlg, ExportedSessionKey, Mode):
1111    if NegFlg.NEGOTIATE_EXTENDED_SESSIONSECURITY:
1112        if NegFlg.NEGOTIATE_128:
1113            SealKey = ExportedSessionKey
1114        elif NegFlg.NEGOTIATE_56:
1115            SealKey = ExportedSessionKey[:7]
1116        else:
1117            SealKey = ExportedSessionKey[:5]
1118        if Mode == "Client":
1119            return Hash_MD5().digest(
1120                SealKey
1121                + b"session key to client-to-server sealing key magic constant\x00"
1122            )
1123        elif Mode == "Server":
1124            return Hash_MD5().digest(
1125                SealKey
1126                + b"session key to server-to-client sealing key magic constant\x00"
1127            )
1128        else:
1129            raise ValueError("Unknown Mode")
1130    elif NegFlg.NEGOTIATE_LM_KEY:
1131        if NegFlg.NEGOTIATE_56:
1132            return ExportedSessionKey[:6] + b"\xA0"
1133        else:
1134            return ExportedSessionKey[:4] + b"\xe5\x38\xb0"
1135    else:
1136        return ExportedSessionKey
1137
1138
1139# --- SSP
1140
1141
1142class NTLMSSP(SSP):
1143    """
1144    The NTLM SSP
1145
1146    Common arguments:
1147
1148        :param auth_level: One of DCE_C_AUTHN_LEVEL
1149        :param USE_MIC: whether to use a MIC or not (default: True)
1150        :param NTLM_VALUES: a dictionary used to override the following values
1151
1152        In case of a client::
1153
1154            - NegotiateFlags
1155            - ProductMajorVersion
1156            - ProductMinorVersion
1157            - ProductBuild
1158
1159        In case of a server::
1160
1161            - NetbiosDomainName
1162            - NetbiosComputerName
1163            - DnsComputerName
1164            - DnsDomainName (defaults to DOMAIN)
1165            - DnsTreeName (defaults to DOMAIN)
1166            - Flags
1167            - Timestamp
1168
1169    Client-only arguments:
1170
1171        :param UPN: the UPN to use for NTLM auth. If no domain is specified, will
1172                    use the one provided by the server (domain in a domain, local
1173                    if without domain)
1174        :param HASHNT: the password to use for NTLM auth
1175        :param PASSWORD: the password to use for NTLM auth
1176
1177    Server-only arguments:
1178
1179        :param DOMAIN_NB_NAME: the domain Netbios name (default: DOMAIN)
1180        :param DOMAIN_FQDN: the domain FQDN (default: <domain_nb_name>.local)
1181        :param COMPUTER_NB_NAME: the server Netbios name (default: SRV)
1182        :param COMPUTER_FQDN: the server FQDN
1183                              (default: <computer_nb_name>.<domain_fqdn>)
1184        :param IDENTITIES: a dict {"username": <HashNT>}
1185                        Setting this value enables signature computation and
1186                        authenticates inbound users.
1187    """
1188
1189    oid = "1.3.6.1.4.1.311.2.2.10"
1190    auth_type = 0x0A
1191
1192    class STATE(SSP.STATE):
1193        INIT = 1
1194        CLI_SENT_NEGO = 2
1195        CLI_SENT_AUTH = 3
1196        SRV_SENT_CHAL = 4
1197
1198    class CONTEXT(SSP.CONTEXT):
1199        __slots__ = [
1200            "SessionKey",
1201            "ExportedSessionKey",
1202            "IsAcceptor",
1203            "SendSignKey",
1204            "SendSealKey",
1205            "RecvSignKey",
1206            "RecvSealKey",
1207            "SendSealHandle",
1208            "RecvSealHandle",
1209            "SendSeqNum",
1210            "RecvSeqNum",
1211            "neg_tok",
1212            "chall_tok",
1213            "ServerHostname",
1214        ]
1215
1216        def __init__(self, IsAcceptor, req_flags=None):
1217            self.state = NTLMSSP.STATE.INIT
1218            self.SessionKey = None
1219            self.ExportedSessionKey = None
1220            self.SendSignKey = None
1221            self.SendSealKey = None
1222            self.SendSealHandle = None
1223            self.RecvSignKey = None
1224            self.RecvSealKey = None
1225            self.RecvSealHandle = None
1226            self.SendSeqNum = 0
1227            self.RecvSeqNum = 0
1228            self.neg_tok = None
1229            self.chall_tok = None
1230            self.ServerHostname = None
1231            self.IsAcceptor = IsAcceptor
1232            super(NTLMSSP.CONTEXT, self).__init__(req_flags=req_flags)
1233
1234        def clifailure(self):
1235            self.__init__(self.IsAcceptor, req_flags=self.flags)
1236
1237        def __repr__(self):
1238            return "NTLMSSP"
1239
1240    def __init__(
1241        self,
1242        UPN=None,
1243        HASHNT=None,
1244        PASSWORD=None,
1245        USE_MIC=True,
1246        NTLM_VALUES={},
1247        DOMAIN_NB_NAME="DOMAIN",
1248        DOMAIN_FQDN=None,
1249        COMPUTER_NB_NAME="SRV",
1250        COMPUTER_FQDN=None,
1251        IDENTITIES=None,
1252        DO_NOT_CHECK_LOGIN=False,
1253        SERVER_CHALLENGE=None,
1254        **kwargs,
1255    ):
1256        self.UPN = UPN
1257        if HASHNT is None and PASSWORD is not None:
1258            HASHNT = MD4le(PASSWORD)
1259        self.HASHNT = HASHNT
1260        self.USE_MIC = USE_MIC
1261        self.NTLM_VALUES = NTLM_VALUES
1262        self.DOMAIN_NB_NAME = DOMAIN_NB_NAME
1263        self.DOMAIN_FQDN = DOMAIN_FQDN or (self.DOMAIN_NB_NAME.lower() + ".local")
1264        self.COMPUTER_NB_NAME = COMPUTER_NB_NAME
1265        self.COMPUTER_FQDN = COMPUTER_FQDN or (
1266            self.COMPUTER_NB_NAME.lower() + "." + self.DOMAIN_FQDN
1267        )
1268        self.IDENTITIES = IDENTITIES
1269        self.DO_NOT_CHECK_LOGIN = DO_NOT_CHECK_LOGIN
1270        self.SERVER_CHALLENGE = SERVER_CHALLENGE
1271        super(NTLMSSP, self).__init__(**kwargs)
1272
1273    def LegsAmount(self, Context: CONTEXT):
1274        return 3
1275
1276    def GSS_GetMICEx(self, Context, msgs, qop_req=0):
1277        """
1278        [MS-NLMP] sect 3.4.8
1279        """
1280        # Concatenate the ToSign
1281        ToSign = b"".join(x.data for x in msgs if x.sign)
1282        sig = MAC(
1283            Context.SendSealHandle,
1284            Context.SendSignKey,
1285            Context.SendSeqNum,
1286            ToSign,
1287        )
1288        Context.SendSeqNum += 1
1289        return sig
1290
1291    def GSS_VerifyMICEx(self, Context, msgs, signature):
1292        """
1293        [MS-NLMP] sect 3.4.9
1294        """
1295        Context.RecvSeqNum = signature.SeqNum
1296        # Concatenate the ToSign
1297        ToSign = b"".join(x.data for x in msgs if x.sign)
1298        sig = MAC(
1299            Context.RecvSealHandle,
1300            Context.RecvSignKey,
1301            Context.RecvSeqNum,
1302            ToSign,
1303        )
1304        if sig.Checksum != signature.Checksum:
1305            raise ValueError("ERROR: Checksums don't match")
1306
1307    def GSS_WrapEx(self, Context, msgs, qop_req=0):
1308        """
1309        [MS-NLMP] sect 3.4.6
1310        """
1311        msgs_cpy = copy.deepcopy(msgs)  # Keep copy for signature
1312        # Encrypt
1313        for msg in msgs:
1314            if msg.conf_req_flag:
1315                msg.data = RC4(Context.SendSealHandle, msg.data)
1316        # Sign
1317        sig = self.GSS_GetMICEx(Context, msgs_cpy, qop_req=qop_req)
1318        return (
1319            msgs,
1320            sig,
1321        )
1322
1323    def GSS_UnwrapEx(self, Context, msgs, signature):
1324        """
1325        [MS-NLMP] sect 3.4.7
1326        """
1327        # Decrypt
1328        for msg in msgs:
1329            if msg.conf_req_flag:
1330                msg.data = RC4(Context.RecvSealHandle, msg.data)
1331        # Check signature
1332        self.GSS_VerifyMICEx(Context, msgs, signature)
1333        return msgs
1334
1335    def canMechListMIC(self, Context):
1336        if not self.USE_MIC:
1337            # RFC 4178
1338            # "If the mechanism selected by the negotiation does not support integrity
1339            # protection, then no mechlistMIC token is used."
1340            return False
1341        if not Context or not Context.SessionKey:
1342            # Not available yet
1343            return False
1344        return True
1345
1346    def getMechListMIC(self, Context, input):
1347        # [MS-SPNG]
1348        # "When NTLM is negotiated, the SPNG server MUST set OriginalHandle to
1349        # ServerHandle before generating the mechListMIC, then set ServerHandle to
1350        # OriginalHandle after generating the mechListMIC."
1351        OriginalHandle = Context.SendSealHandle
1352        Context.SendSealHandle = RC4Init(Context.SendSealKey)
1353        try:
1354            return super(NTLMSSP, self).getMechListMIC(Context, input)
1355        finally:
1356            Context.SendSealHandle = OriginalHandle
1357
1358    def verifyMechListMIC(self, Context, otherMIC, input):
1359        # [MS-SPNG]
1360        # "the SPNEGO Extension server MUST set OriginalHandle to ClientHandle before
1361        # validating the mechListMIC and then set ClientHandle to OriginalHandle after
1362        # validating the mechListMIC."
1363        OriginalHandle = Context.RecvSealHandle
1364        Context.RecvSealHandle = RC4Init(Context.RecvSealKey)
1365        try:
1366            return super(NTLMSSP, self).verifyMechListMIC(Context, otherMIC, input)
1367        finally:
1368            Context.RecvSealHandle = OriginalHandle
1369
1370    def GSS_Init_sec_context(
1371        self, Context: CONTEXT, val=None, req_flags: Optional[GSS_C_FLAGS] = None
1372    ):
1373        if Context is None:
1374            Context = self.CONTEXT(False, req_flags=req_flags)
1375
1376        if Context.state == self.STATE.INIT:
1377            # Client: negotiate
1378            # Create a default token
1379            tok = NTLM_NEGOTIATE(
1380                NegotiateFlags="+".join(
1381                    [
1382                        "NEGOTIATE_UNICODE",
1383                        "REQUEST_TARGET",
1384                        "NEGOTIATE_NTLM",
1385                        "NEGOTIATE_ALWAYS_SIGN",
1386                        "TARGET_TYPE_DOMAIN",
1387                        "NEGOTIATE_EXTENDED_SESSIONSECURITY",
1388                        "NEGOTIATE_TARGET_INFO",
1389                        "NEGOTIATE_VERSION",
1390                        "NEGOTIATE_128",
1391                        "NEGOTIATE_56",
1392                    ]
1393                    + (
1394                        [
1395                            "NEGOTIATE_KEY_EXCH",
1396                        ]
1397                        if Context.flags
1398                        & (GSS_C_FLAGS.GSS_C_INTEG_FLAG | GSS_C_FLAGS.GSS_C_CONF_FLAG)
1399                        else []
1400                    )
1401                    + (
1402                        [
1403                            "NEGOTIATE_SIGN",
1404                        ]
1405                        if Context.flags & GSS_C_FLAGS.GSS_C_INTEG_FLAG
1406                        else []
1407                    )
1408                    + (
1409                        [
1410                            "NEGOTIATE_SEAL",
1411                        ]
1412                        if Context.flags & GSS_C_FLAGS.GSS_C_CONF_FLAG
1413                        else []
1414                    )
1415                ),
1416                ProductMajorVersion=10,
1417                ProductMinorVersion=0,
1418                ProductBuild=19041,
1419            )
1420            if self.NTLM_VALUES:
1421                # Update that token with the customs one
1422                for key in [
1423                    "NegotiateFlags",
1424                    "ProductMajorVersion",
1425                    "ProductMinorVersion",
1426                    "ProductBuild",
1427                ]:
1428                    if key in self.NTLM_VALUES:
1429                        setattr(tok, key, self.NTLM_VALUES[key])
1430            Context.neg_tok = tok
1431            Context.SessionKey = None  # Reset signing (if previous auth failed)
1432            Context.state = self.STATE.CLI_SENT_NEGO
1433            return Context, tok, GSS_S_CONTINUE_NEEDED
1434        elif Context.state == self.STATE.CLI_SENT_NEGO:
1435            # Client: auth (val=challenge)
1436            chall_tok = val
1437            if self.UPN is None or self.HASHNT is None:
1438                raise ValueError(
1439                    "Must provide a 'UPN' and a 'HASHNT' or 'PASSWORD' when "
1440                    "running in standalone !"
1441                )
1442            if not chall_tok or NTLM_CHALLENGE not in chall_tok:
1443                log_runtime.debug("NTLMSSP: Unexpected token. Expected NTLM Challenge")
1444                return Context, None, GSS_S_DEFECTIVE_TOKEN
1445            # Take a default token
1446            tok = NTLM_AUTHENTICATE_V2(
1447                NegotiateFlags=chall_tok.NegotiateFlags,
1448                ProductMajorVersion=10,
1449                ProductMinorVersion=0,
1450                ProductBuild=19041,
1451            )
1452            tok.LmChallengeResponse = LMv2_RESPONSE()
1453            from scapy.layers.kerberos import _parse_upn
1454
1455            try:
1456                tok.UserName, realm = _parse_upn(self.UPN)
1457            except ValueError:
1458                tok.UserName, realm = self.UPN, None
1459            if realm is None:
1460                try:
1461                    tok.DomainName = chall_tok.getAv(0x0002).Value
1462                except IndexError:
1463                    log_runtime.warning(
1464                        "No realm specified in UPN, nor provided by server"
1465                    )
1466                    tok.DomainName = self.DOMAIN_NB_NAME.encode()
1467            else:
1468                tok.DomainName = realm
1469            try:
1470                tok.Workstation = Context.ServerHostname = chall_tok.getAv(
1471                    0x0001
1472                ).Value  # noqa: E501
1473            except IndexError:
1474                tok.Workstation = "WIN"
1475            cr = tok.NtChallengeResponse = NTLMv2_RESPONSE(
1476                ChallengeFromClient=os.urandom(8),
1477            )
1478            try:
1479                # the server SHOULD set the timestamp in the CHALLENGE_MESSAGE
1480                cr.TimeStamp = chall_tok.getAv(0x0007).Value
1481            except IndexError:
1482                cr.TimeStamp = int((time.time() + 11644473600) * 1e7)
1483            cr.AvPairs = (
1484                chall_tok.TargetInfo[:-1]
1485                + (
1486                    [
1487                        AV_PAIR(AvId="MsvAvFlags", Value="MIC integrity"),
1488                    ]
1489                    if self.USE_MIC
1490                    else []
1491                )
1492                + [
1493                    AV_PAIR(
1494                        AvId="MsvAvSingleHost",
1495                        Value=Single_Host_Data(MachineID=os.urandom(32)),
1496                    ),
1497                    AV_PAIR(AvId="MsvAvChannelBindings", Value=b"\x00" * 16),
1498                    AV_PAIR(AvId="MsvAvTargetName", Value="host/" + tok.Workstation),
1499                    AV_PAIR(AvId="MsvAvEOL"),
1500                ]
1501            )
1502            if self.NTLM_VALUES:
1503                # Update that token with the customs one
1504                for key in [
1505                    "NegotiateFlags",
1506                    "ProductMajorVersion",
1507                    "ProductMinorVersion",
1508                    "ProductBuild",
1509                ]:
1510                    if key in self.NTLM_VALUES:
1511                        setattr(tok, key, self.NTLM_VALUES[key])
1512            # Compute the ResponseKeyNT
1513            ResponseKeyNT = NTOWFv2(
1514                None,
1515                tok.UserName,
1516                tok.DomainName,
1517                HashNt=self.HASHNT,
1518            )
1519            # Compute the NTProofStr
1520            cr.NTProofStr = cr.computeNTProofStr(
1521                ResponseKeyNT,
1522                chall_tok.ServerChallenge,
1523            )
1524            # Compute the Session Key
1525            SessionBaseKey = NTLMv2_ComputeSessionBaseKey(ResponseKeyNT, cr.NTProofStr)
1526            KeyExchangeKey = SessionBaseKey  # Only true for NTLMv2
1527            if chall_tok.NegotiateFlags.NEGOTIATE_KEY_EXCH:
1528                ExportedSessionKey = os.urandom(16)
1529                tok.EncryptedRandomSessionKey = RC4K(
1530                    KeyExchangeKey,
1531                    ExportedSessionKey,
1532                )
1533            else:
1534                ExportedSessionKey = KeyExchangeKey
1535            if self.USE_MIC:
1536                tok.compute_mic(ExportedSessionKey, Context.neg_tok, chall_tok)
1537            Context.ExportedSessionKey = ExportedSessionKey
1538            # [MS-SMB] 3.2.5.3
1539            Context.SessionKey = Context.ExportedSessionKey
1540            # Compute NTLM keys
1541            Context.SendSignKey = SIGNKEY(
1542                tok.NegotiateFlags, ExportedSessionKey, "Client"
1543            )
1544            Context.SendSealKey = SEALKEY(
1545                tok.NegotiateFlags, ExportedSessionKey, "Client"
1546            )
1547            Context.SendSealHandle = RC4Init(Context.SendSealKey)
1548            Context.RecvSignKey = SIGNKEY(
1549                tok.NegotiateFlags, ExportedSessionKey, "Server"
1550            )
1551            Context.RecvSealKey = SEALKEY(
1552                tok.NegotiateFlags, ExportedSessionKey, "Server"
1553            )
1554            Context.RecvSealHandle = RC4Init(Context.RecvSealKey)
1555            Context.state = self.STATE.CLI_SENT_AUTH
1556            return Context, tok, GSS_S_COMPLETE
1557        elif Context.state == self.STATE.CLI_SENT_AUTH:
1558            if val:
1559                # what is that?
1560                status = GSS_S_DEFECTIVE_CREDENTIAL
1561            else:
1562                status = GSS_S_COMPLETE
1563            return Context, None, status
1564        else:
1565            raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state))
1566
1567    def GSS_Accept_sec_context(self, Context: CONTEXT, val=None):
1568        if Context is None:
1569            Context = self.CONTEXT(IsAcceptor=True, req_flags=0)
1570
1571        if Context.state == self.STATE.INIT:
1572            # Server: challenge (val=negotiate)
1573            nego_tok = val
1574            if not nego_tok or NTLM_NEGOTIATE not in nego_tok:
1575                log_runtime.debug("NTLMSSP: Unexpected token. Expected NTLM Negotiate")
1576                return Context, None, GSS_S_DEFECTIVE_TOKEN
1577            # Take a default token
1578            currentTime = (time.time() + 11644473600) * 1e7
1579            tok = NTLM_CHALLENGE(
1580                ServerChallenge=self.SERVER_CHALLENGE or os.urandom(8),
1581                NegotiateFlags="+".join(
1582                    [
1583                        "NEGOTIATE_UNICODE",
1584                        "REQUEST_TARGET",
1585                        "NEGOTIATE_NTLM",
1586                        "NEGOTIATE_ALWAYS_SIGN",
1587                        "NEGOTIATE_EXTENDED_SESSIONSECURITY",
1588                        "NEGOTIATE_TARGET_INFO",
1589                        "TARGET_TYPE_DOMAIN",
1590                        "NEGOTIATE_VERSION",
1591                        "NEGOTIATE_128",
1592                        "NEGOTIATE_KEY_EXCH",
1593                        "NEGOTIATE_56",
1594                    ]
1595                    + (
1596                        ["NEGOTIATE_SIGN"]
1597                        if nego_tok.NegotiateFlags.NEGOTIATE_SIGN
1598                        else []
1599                    )
1600                    + (
1601                        ["NEGOTIATE_SEAL"]
1602                        if nego_tok.NegotiateFlags.NEGOTIATE_SEAL
1603                        else []
1604                    )
1605                ),
1606                ProductMajorVersion=10,
1607                ProductMinorVersion=0,
1608                Payload=[
1609                    ("TargetName", ""),
1610                    (
1611                        "TargetInfo",
1612                        [
1613                            # MsvAvNbComputerName
1614                            AV_PAIR(AvId=1, Value=self.COMPUTER_NB_NAME),
1615                            # MsvAvNbDomainName
1616                            AV_PAIR(AvId=2, Value=self.DOMAIN_NB_NAME),
1617                            # MsvAvDnsComputerName
1618                            AV_PAIR(AvId=3, Value=self.COMPUTER_FQDN),
1619                            # MsvAvDnsDomainName
1620                            AV_PAIR(AvId=4, Value=self.DOMAIN_FQDN),
1621                            # MsvAvDnsTreeName
1622                            AV_PAIR(AvId=5, Value=self.DOMAIN_FQDN),
1623                            # MsvAvTimestamp
1624                            AV_PAIR(AvId=7, Value=currentTime),
1625                            # MsvAvEOL
1626                            AV_PAIR(AvId=0),
1627                        ],
1628                    ),
1629                ],
1630            )
1631            if self.NTLM_VALUES:
1632                # Update that token with the customs one
1633                for key in [
1634                    "ServerChallenge",
1635                    "NegotiateFlags",
1636                    "ProductMajorVersion",
1637                    "ProductMinorVersion",
1638                    "TargetName",
1639                ]:
1640                    if key in self.NTLM_VALUES:
1641                        setattr(tok, key, self.NTLM_VALUES[key])
1642                avpairs = {x.AvId: x.Value for x in tok.TargetInfo}
1643                tok.TargetInfo = [
1644                    AV_PAIR(AvId=i, Value=self.NTLM_VALUES.get(x, avpairs[i]))
1645                    for (i, x) in [
1646                        (2, "NetbiosDomainName"),
1647                        (1, "NetbiosComputerName"),
1648                        (4, "DnsDomainName"),
1649                        (3, "DnsComputerName"),
1650                        (5, "DnsTreeName"),
1651                        (6, "Flags"),
1652                        (7, "Timestamp"),
1653                        (0, None),
1654                    ]
1655                    if ((x in self.NTLM_VALUES) or (i in avpairs))
1656                    and self.NTLM_VALUES.get(x, True) is not None
1657                ]
1658            Context.chall_tok = tok
1659            Context.state = self.STATE.SRV_SENT_CHAL
1660            return Context, tok, GSS_S_CONTINUE_NEEDED
1661        elif Context.state == self.STATE.SRV_SENT_CHAL:
1662            # server: OK or challenge again (val=auth)
1663            auth_tok = val
1664            if not auth_tok or NTLM_AUTHENTICATE_V2 not in auth_tok:
1665                log_runtime.debug(
1666                    "NTLMSSP: Unexpected token. Expected NTLM Authenticate v2"
1667                )
1668                return Context, None, GSS_S_DEFECTIVE_TOKEN
1669            if self.DO_NOT_CHECK_LOGIN:
1670                # Just trust me bro
1671                return Context, None, GSS_S_COMPLETE
1672            SessionBaseKey = self._getSessionBaseKey(Context, auth_tok)
1673            if SessionBaseKey:
1674                # [MS-NLMP] sect 3.2.5.1.2
1675                KeyExchangeKey = SessionBaseKey  # Only true for NTLMv2
1676                if auth_tok.NegotiateFlags.NEGOTIATE_KEY_EXCH:
1677                    if not auth_tok.EncryptedRandomSessionKeyLen:
1678                        # No EncryptedRandomSessionKey. libcurl for instance
1679                        # hmm. this looks bad
1680                        EncryptedRandomSessionKey = b"\x00" * 16
1681                    else:
1682                        EncryptedRandomSessionKey = auth_tok.EncryptedRandomSessionKey
1683                    ExportedSessionKey = RC4K(
1684                        KeyExchangeKey, EncryptedRandomSessionKey
1685                    )
1686                else:
1687                    ExportedSessionKey = KeyExchangeKey
1688                Context.ExportedSessionKey = ExportedSessionKey
1689                # [MS-SMB] 3.2.5.3
1690                Context.SessionKey = Context.ExportedSessionKey
1691            # Check the NTProofStr
1692            if Context.SessionKey:
1693                # Compute NTLM keys
1694                Context.SendSignKey = SIGNKEY(
1695                    auth_tok.NegotiateFlags, ExportedSessionKey, "Server"
1696                )
1697                Context.SendSealKey = SEALKEY(
1698                    auth_tok.NegotiateFlags, ExportedSessionKey, "Server"
1699                )
1700                Context.SendSealHandle = RC4Init(Context.SendSealKey)
1701                Context.RecvSignKey = SIGNKEY(
1702                    auth_tok.NegotiateFlags, ExportedSessionKey, "Client"
1703                )
1704                Context.RecvSealKey = SEALKEY(
1705                    auth_tok.NegotiateFlags, ExportedSessionKey, "Client"
1706                )
1707                Context.RecvSealHandle = RC4Init(Context.RecvSealKey)
1708                if self._checkLogin(Context, auth_tok):
1709                    # Set negotiated flags
1710                    if auth_tok.NegotiateFlags.NEGOTIATE_SIGN:
1711                        Context.flags |= GSS_C_FLAGS.GSS_C_INTEG_FLAG
1712                    if auth_tok.NegotiateFlags.NEGOTIATE_SEAL:
1713                        Context.flags |= GSS_C_FLAGS.GSS_C_CONF_FLAG
1714                    return Context, None, GSS_S_COMPLETE
1715            # Bad NTProofStr or unknown user
1716            Context.SessionKey = None
1717            Context.state = self.STATE.INIT
1718            return Context, None, GSS_S_DEFECTIVE_CREDENTIAL
1719        else:
1720            raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state))
1721
1722    def MaximumSignatureLength(self, Context: CONTEXT):
1723        """
1724        Returns the Maximum Signature length.
1725
1726        This will be used in auth_len in DceRpc5, and is necessary for
1727        PFC_SUPPORT_HEADER_SIGN to work properly.
1728        """
1729        return 16  # len(NTLMSSP_MESSAGE_SIGNATURE())
1730
1731    def GSS_Passive(self, Context: CONTEXT, val=None):
1732        if Context is None:
1733            Context = self.CONTEXT(True)
1734            Context.passive = True
1735
1736        # We capture the Negotiate, Challenge, then call the server's auth handling
1737        # and discard the output.
1738
1739        if Context.state == self.STATE.INIT:
1740            if not val or NTLM_NEGOTIATE not in val:
1741                log_runtime.warning("NTLMSSP: Expected NTLM Negotiate")
1742                return None, GSS_S_DEFECTIVE_TOKEN
1743            Context.neg_tok = val
1744            Context.state = self.STATE.CLI_SENT_NEGO
1745            return Context, GSS_S_CONTINUE_NEEDED
1746        elif Context.state == self.STATE.CLI_SENT_NEGO:
1747            if not val or NTLM_CHALLENGE not in val:
1748                log_runtime.warning("NTLMSSP: Expected NTLM Challenge")
1749                return None, GSS_S_DEFECTIVE_TOKEN
1750            Context.chall_tok = val
1751            Context.state = self.STATE.SRV_SENT_CHAL
1752            return Context, GSS_S_CONTINUE_NEEDED
1753        elif Context.state == self.STATE.SRV_SENT_CHAL:
1754            if not val or NTLM_AUTHENTICATE_V2 not in val:
1755                log_runtime.warning("NTLMSSP: Expected NTLM Authenticate")
1756                return None, GSS_S_DEFECTIVE_TOKEN
1757            Context, _, status = self.GSS_Accept_sec_context(Context, val)
1758            if status != GSS_S_COMPLETE:
1759                log_runtime.info("NTLMSSP: auth failed.")
1760            Context.state = self.STATE.INIT
1761            return Context, status
1762        else:
1763            raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state))
1764
1765    def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False):
1766        if Context.IsAcceptor is not IsAcceptor:
1767            return
1768        # Swap everything
1769        Context.SendSignKey, Context.RecvSignKey = (
1770            Context.RecvSignKey,
1771            Context.SendSignKey,
1772        )
1773        Context.SendSealKey, Context.RecvSealKey = (
1774            Context.RecvSealKey,
1775            Context.SendSealKey,
1776        )
1777        Context.SendSealHandle, Context.RecvSealHandle = (
1778            Context.RecvSealHandle,
1779            Context.SendSealHandle,
1780        )
1781        Context.SendSeqNum, Context.RecvSeqNum = Context.RecvSeqNum, Context.SendSeqNum
1782        Context.IsAcceptor = not Context.IsAcceptor
1783
1784    def _getSessionBaseKey(self, Context, auth_tok):
1785        """
1786        Function that returns the SessionBaseKey from the ntlm Authenticate.
1787        """
1788        if auth_tok.UserNameLen:
1789            username = auth_tok.UserName
1790        else:
1791            username = None
1792        if auth_tok.DomainNameLen:
1793            domain = auth_tok.DomainName
1794        else:
1795            domain = ""
1796        if self.IDENTITIES and username in self.IDENTITIES:
1797            ResponseKeyNT = NTOWFv2(
1798                None, username, domain, HashNt=self.IDENTITIES[username]
1799            )
1800            return NTLMv2_ComputeSessionBaseKey(
1801                ResponseKeyNT, auth_tok.NtChallengeResponse.NTProofStr
1802            )
1803        return None
1804
1805    def _checkLogin(self, Context, auth_tok):
1806        """
1807        Function that checks the validity of an authentication.
1808
1809        Overwrite and return True to bypass.
1810        """
1811        # Create the NTLM AUTH
1812        if auth_tok.UserNameLen:
1813            username = auth_tok.UserName
1814        else:
1815            username = None
1816        if auth_tok.DomainNameLen:
1817            domain = auth_tok.DomainName
1818        else:
1819            domain = ""
1820        if username in self.IDENTITIES:
1821            ResponseKeyNT = NTOWFv2(
1822                None, username, domain, HashNt=self.IDENTITIES[username]
1823            )
1824            NTProofStr = auth_tok.NtChallengeResponse.computeNTProofStr(
1825                ResponseKeyNT,
1826                Context.chall_tok.ServerChallenge,
1827            )
1828            if NTProofStr == auth_tok.NtChallengeResponse.NTProofStr:
1829                return True
1830        return False
1831