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