Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/layers/ntlm.py: 35%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

740 statements  

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 _GSSAPI_OIDS, 

64 _GSSAPI_SIGNATURE_OIDS, 

65 GSS_C_FLAGS, 

66 GSS_C_NO_CHANNEL_BINDINGS, 

67 GSS_S_BAD_BINDINGS, 

68 GSS_S_COMPLETE, 

69 GSS_S_CONTINUE_NEEDED, 

70 GSS_S_DEFECTIVE_CREDENTIAL, 

71 GSS_S_DEFECTIVE_TOKEN, 

72 GSS_S_FLAGS, 

73 GssChannelBindings, 

74 SSP, 

75) 

76 

77# Typing imports 

78from typing import ( 

79 Any, 

80 Callable, 

81 List, 

82 Optional, 

83 Tuple, 

84 Union, 

85) 

86 

87# Crypto imports 

88 

89from scapy.layers.tls.crypto.hash import Hash_MD4, Hash_MD5 

90from scapy.layers.tls.crypto.h_mac import Hmac_MD5 

91 

92########## 

93# Fields # 

94########## 

95 

96 

97# NTLM structures are all in all very complicated. Many fields don't have a fixed 

98# position, but are rather referred to with an offset (from the beginning of the 

99# structure) and a length. In addition to that, there are variants of the structure 

100# with missing fields when running old versions of Windows (sometimes also seen when 

101# talking to products that reimplement NTLM, most notably backup applications). 

102 

103# We add `_NTLMPayloadField` and `_NTLMPayloadPacket` to parse fields that use an 

104# offset, and `_NTLM_post_build` to be able to rebuild those offsets. 

105# In addition, the `NTLM_VARIANT*` allows to select what flavor of NTLM to use 

106# (NT, XP, or Recent). But in real world use only Recent should be used. 

107 

108 

109class _NTLMPayloadField(_StrField[List[Tuple[str, Any]]]): 

110 """Special field used to dissect NTLM payloads. 

111 This isn't trivial because the offsets are variable.""" 

112 

113 __slots__ = [ 

114 "fields", 

115 "fields_map", 

116 "offset", 

117 "length_from", 

118 "force_order", 

119 "offset_name", 

120 ] 

121 islist = True 

122 

123 def __init__( 

124 self, 

125 name, # type: str 

126 offset, # type: Union[int, Callable[[Packet], int]] 

127 fields, # type: List[Field[Any, Any]] 

128 length_from=None, # type: Optional[Callable[[Packet], int]] 

129 force_order=None, # type: Optional[List[str]] 

130 offset_name="BufferOffset", # type: str 

131 ): 

132 # type: (...) -> None 

133 self.offset = offset 

134 self.fields = fields 

135 self.fields_map = {field.name: field for field in fields} 

136 self.length_from = length_from 

137 self.force_order = force_order # whether the order of fields is fixed 

138 self.offset_name = offset_name 

139 super(_NTLMPayloadField, self).__init__( 

140 name, 

141 [ 

142 (field.name, field.default) 

143 for field in fields 

144 if field.default is not None 

145 ], 

146 ) 

147 

148 def _on_payload(self, pkt, x, func): 

149 # type: (Optional[Packet], bytes, str) -> List[Tuple[str, Any]] 

150 if not pkt or not x: 

151 return [] 

152 results = [] 

153 for field_name, value in x: 

154 if field_name not in self.fields_map: 

155 continue 

156 if not isinstance( 

157 self.fields_map[field_name], PacketListField 

158 ) and not isinstance(value, Packet): 

159 value = getattr(self.fields_map[field_name], func)(pkt, value) 

160 results.append((field_name, value)) 

161 return results 

162 

163 def i2h(self, pkt, x): 

164 # type: (Optional[Packet], bytes) -> List[Tuple[str, str]] 

165 return self._on_payload(pkt, x, "i2h") 

166 

167 def h2i(self, pkt, x): 

168 # type: (Optional[Packet], bytes) -> List[Tuple[str, str]] 

169 return self._on_payload(pkt, x, "h2i") 

170 

171 def i2repr(self, pkt, x): 

172 # type: (Optional[Packet], bytes) -> str 

173 return repr(self._on_payload(pkt, x, "i2repr")) 

174 

175 def _o_pkt(self, pkt): 

176 # type: (Optional[Packet]) -> int 

177 if callable(self.offset): 

178 return self.offset(pkt) 

179 return self.offset 

180 

181 def addfield(self, pkt, s, val): 

182 # type: (Optional[Packet], bytes, Optional[List[Tuple[str, str]]]) -> bytes 

183 # Create string buffer 

184 buf = StringBuffer() 

185 buf.append(s, 1) 

186 # Calc relative offset 

187 r_off = self._o_pkt(pkt) - len(s) 

188 if self.force_order: 

189 val.sort(key=lambda x: self.force_order.index(x[0])) 

190 for field_name, value in val: 

191 if field_name not in self.fields_map: 

192 continue 

193 field = self.fields_map[field_name] 

194 offset = pkt.getfieldval(field_name + self.offset_name) 

195 if offset is None: 

196 # No offset specified: calc 

197 offset = len(buf) 

198 else: 

199 # Calc relative offset 

200 offset -= r_off 

201 pad = offset + 1 - len(buf) 

202 # Add padding if necessary 

203 if pad > 0: 

204 buf.append(pad * b"\x00", len(buf)) 

205 buf.append(field.addfield(pkt, bytes(buf), value)[len(buf) :], offset + 1) 

206 return bytes(buf) 

207 

208 def getfield(self, pkt, s): 

209 # type: (Packet, bytes) -> Tuple[bytes, List[Tuple[str, str]]] 

210 if self.length_from is None: 

211 ret, remain = b"", s 

212 else: 

213 len_pkt = self.length_from(pkt) 

214 ret, remain = s[len_pkt:], s[:len_pkt] 

215 if not pkt or not remain: 

216 return s, [] 

217 results = [] 

218 max_offset = 0 

219 o_pkt = self._o_pkt(pkt) 

220 offsets = [ 

221 pkt.getfieldval(x.name + self.offset_name) - o_pkt for x in self.fields 

222 ] 

223 for i, field in enumerate(self.fields): 

224 offset = offsets[i] 

225 try: 

226 length = pkt.getfieldval(field.name + "Len") 

227 except AttributeError: 

228 length = len(remain) - offset 

229 # length can't be greater than the difference with the next offset 

230 try: 

231 length = min(length, min(x - offset for x in offsets if x > offset)) 

232 except ValueError: 

233 pass 

234 if offset < 0: 

235 continue 

236 max_offset = max(offset + length, max_offset) 

237 if remain[offset : offset + length]: 

238 results.append( 

239 ( 

240 offset, 

241 field.name, 

242 field.getfield(pkt, remain[offset : offset + length])[1], 

243 ) 

244 ) 

245 ret += remain[max_offset:] 

246 results.sort(key=lambda x: x[0]) 

247 return ret, [x[1:] for x in results] 

248 

249 

250class _NTLMPayloadPacket(Packet): 

251 _NTLM_PAYLOAD_FIELD_NAME = "Payload" 

252 

253 def __init__( 

254 self, 

255 _pkt=b"", # type: Union[bytes, bytearray] 

256 post_transform=None, # type: Any 

257 _internal=0, # type: int 

258 _underlayer=None, # type: Optional[Packet] 

259 _parent=None, # type: Optional[Packet] 

260 **fields, # type: Any 

261 ): 

262 # pop unknown fields. We can't process them until the packet is initialized 

263 unknown = { 

264 k: fields.pop(k) 

265 for k in list(fields) 

266 if not any(k == f.name for f in self.fields_desc) 

267 } 

268 super(_NTLMPayloadPacket, self).__init__( 

269 _pkt=_pkt, 

270 post_transform=post_transform, 

271 _internal=_internal, 

272 _underlayer=_underlayer, 

273 _parent=_parent, 

274 **fields, 

275 ) 

276 # check unknown fields for implicit ones 

277 local_fields = next( 

278 [y.name for y in x.fields] 

279 for x in self.fields_desc 

280 if x.name == self._NTLM_PAYLOAD_FIELD_NAME 

281 ) 

282 implicit_fields = {k: v for k, v in unknown.items() if k in local_fields} 

283 for k, value in implicit_fields.items(): 

284 self.setfieldval(k, value) 

285 

286 def getfieldval(self, attr): 

287 # Ease compatibility with _NTLMPayloadField 

288 try: 

289 return super(_NTLMPayloadPacket, self).getfieldval(attr) 

290 except AttributeError: 

291 try: 

292 return next( 

293 x[1] 

294 for x in super(_NTLMPayloadPacket, self).getfieldval( 

295 self._NTLM_PAYLOAD_FIELD_NAME 

296 ) 

297 if x[0] == attr 

298 ) 

299 except StopIteration: 

300 raise AttributeError(attr) 

301 

302 def getfield_and_val(self, attr): 

303 # Ease compatibility with _NTLMPayloadField 

304 try: 

305 return super(_NTLMPayloadPacket, self).getfield_and_val(attr) 

306 except ValueError: 

307 PayFields = self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map 

308 try: 

309 return ( 

310 PayFields[attr], 

311 PayFields[attr].h2i( # cancel out the i2h.. it's dumb i know 

312 self, 

313 next( 

314 x[1] 

315 for x in super(_NTLMPayloadPacket, self).__getattr__( 

316 self._NTLM_PAYLOAD_FIELD_NAME 

317 ) 

318 if x[0] == attr 

319 ), 

320 ), 

321 ) 

322 except (StopIteration, KeyError): 

323 raise ValueError(attr) 

324 

325 def setfieldval(self, attr, val): 

326 # Ease compatibility with _NTLMPayloadField 

327 try: 

328 return super(_NTLMPayloadPacket, self).setfieldval(attr, val) 

329 except AttributeError: 

330 Payload = super(_NTLMPayloadPacket, self).__getattr__( 

331 self._NTLM_PAYLOAD_FIELD_NAME 

332 ) 

333 if attr not in self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map: 

334 raise AttributeError(attr) 

335 try: 

336 Payload.pop( 

337 next( 

338 i 

339 for i, x in enumerate( 

340 super(_NTLMPayloadPacket, self).__getattr__( 

341 self._NTLM_PAYLOAD_FIELD_NAME 

342 ) 

343 ) 

344 if x[0] == attr 

345 ) 

346 ) 

347 except StopIteration: 

348 pass 

349 Payload.append([attr, val]) 

350 super(_NTLMPayloadPacket, self).setfieldval( 

351 self._NTLM_PAYLOAD_FIELD_NAME, Payload 

352 ) 

353 

354 

355class _NTLM_ENUM(IntEnum): 

356 LEN = 0x0001 

357 MAXLEN = 0x0002 

358 OFFSET = 0x0004 

359 COUNT = 0x0008 

360 PAD8 = 0x1000 

361 

362 

363_NTLM_CONFIG = [ 

364 ("Len", _NTLM_ENUM.LEN), 

365 ("MaxLen", _NTLM_ENUM.MAXLEN), 

366 ("BufferOffset", _NTLM_ENUM.OFFSET), 

367] 

368 

369 

370def _NTLM_post_build(self, p, pay_offset, fields, config=_NTLM_CONFIG): 

371 """Util function to build the offset and populate the lengths""" 

372 for field_name, value in self.fields[self._NTLM_PAYLOAD_FIELD_NAME]: 

373 fld = self.get_field(self._NTLM_PAYLOAD_FIELD_NAME).fields_map[field_name] 

374 length = fld.i2len(self, value) 

375 count = fld.i2count(self, value) 

376 offset = fields[field_name] 

377 i = 0 

378 r = lambda y: {2: "H", 4: "I", 8: "Q"}[y] 

379 for fname, ftype in config: 

380 if isinstance(ftype, dict): 

381 ftype = ftype[field_name] 

382 if ftype & _NTLM_ENUM.LEN: 

383 fval = length 

384 elif ftype & _NTLM_ENUM.OFFSET: 

385 fval = pay_offset 

386 elif ftype & _NTLM_ENUM.MAXLEN: 

387 fval = length 

388 elif ftype & _NTLM_ENUM.COUNT: 

389 fval = count 

390 else: 

391 raise ValueError 

392 if ftype & _NTLM_ENUM.PAD8: 

393 fval += (-fval) % 8 

394 sz = self.get_field(field_name + fname).sz 

395 if self.getfieldval(field_name + fname) is None: 

396 p = ( 

397 p[: offset + i] 

398 + struct.pack("<%s" % r(sz), fval) 

399 + p[offset + i + sz :] 

400 ) 

401 i += sz 

402 pay_offset += length 

403 return p 

404 

405 

406############## 

407# Structures # 

408############## 

409 

410 

411# -- Util: VARIANT class 

412 

413 

414class NTLM_VARIANT(IntEnum): 

415 """ 

416 The message variant to use for NTLM. 

417 """ 

418 

419 NT_OR_2000 = 0 

420 XP_OR_2003 = 1 

421 RECENT = 2 

422 

423 

424class _NTLM_VARIANT_Packet(_NTLMPayloadPacket): 

425 def __init__(self, *args, **kwargs): 

426 self.VARIANT = kwargs.pop("VARIANT", NTLM_VARIANT.RECENT) 

427 super(_NTLM_VARIANT_Packet, self).__init__(*args, **kwargs) 

428 

429 def clone_with(self, *args, **kwargs): 

430 pkt = super(_NTLM_VARIANT_Packet, self).clone_with(*args, **kwargs) 

431 pkt.VARIANT = self.VARIANT 

432 return pkt 

433 

434 def copy(self): 

435 pkt = super(_NTLM_VARIANT_Packet, self).copy() 

436 pkt.VARIANT = self.VARIANT 

437 

438 return pkt 

439 

440 def show2(self, dump=False, indent=3, lvl="", label_lvl=""): 

441 return self.__class__(bytes(self), VARIANT=self.VARIANT).show( 

442 dump, indent, lvl, label_lvl 

443 ) 

444 

445 

446# Sect 2.2 

447 

448 

449class NTLM_Header(Packet): 

450 name = "NTLM Header" 

451 fields_desc = [ 

452 StrFixedLenField("Signature", b"NTLMSSP\0", length=8), 

453 LEIntEnumField( 

454 "MessageType", 

455 3, 

456 { 

457 1: "NEGOTIATE_MESSAGE", 

458 2: "CHALLENGE_MESSAGE", 

459 3: "AUTHENTICATE_MESSAGE", 

460 }, 

461 ), 

462 ] 

463 

464 @classmethod 

465 def dispatch_hook(cls, _pkt=None, *args, **kargs): 

466 if cls is NTLM_Header and _pkt and len(_pkt) >= 10: 

467 MessageType = struct.unpack("<H", _pkt[8:10])[0] 

468 if MessageType == 1: 

469 return NTLM_NEGOTIATE 

470 elif MessageType == 2: 

471 return NTLM_CHALLENGE 

472 elif MessageType == 3: 

473 return NTLM_AUTHENTICATE_V2 

474 return cls 

475 

476 

477# Sect 2.2.2.5 

478_negotiateFlags = [ 

479 "NEGOTIATE_UNICODE", # A 

480 "NEGOTIATE_OEM", # B 

481 "REQUEST_TARGET", # C 

482 "r10", 

483 "NEGOTIATE_SIGN", # D 

484 "NEGOTIATE_SEAL", # E 

485 "NEGOTIATE_DATAGRAM", # F 

486 "NEGOTIATE_LM_KEY", # G 

487 "r9", 

488 "NEGOTIATE_NTLM", # H 

489 "r8", 

490 "J", 

491 "NEGOTIATE_OEM_DOMAIN_SUPPLIED", # K 

492 "NEGOTIATE_OEM_WORKSTATION_SUPPLIED", # L 

493 "r7", 

494 "NEGOTIATE_ALWAYS_SIGN", # M 

495 "TARGET_TYPE_DOMAIN", # N 

496 "TARGET_TYPE_SERVER", # O 

497 "r6", 

498 "NEGOTIATE_EXTENDED_SESSIONSECURITY", # P 

499 "NEGOTIATE_IDENTIFY", # Q 

500 "r5", 

501 "REQUEST_NON_NT_SESSION_KEY", # R 

502 "NEGOTIATE_TARGET_INFO", # S 

503 "r4", 

504 "NEGOTIATE_VERSION", # T 

505 "r3", 

506 "r2", 

507 "r1", 

508 "NEGOTIATE_128", # U 

509 "NEGOTIATE_KEY_EXCH", # V 

510 "NEGOTIATE_56", # W 

511] 

512 

513 

514def _NTLMStrField(name, default): 

515 return MultipleTypeField( 

516 [ 

517 ( 

518 StrFieldUtf16(name, default), 

519 lambda pkt: pkt.NegotiateFlags.NEGOTIATE_UNICODE, 

520 ) 

521 ], 

522 StrField(name, default), 

523 ) 

524 

525 

526# Sect 2.2.2.10 

527 

528 

529class _NTLM_Version(Packet): 

530 fields_desc = [ 

531 ByteField("ProductMajorVersion", 0), 

532 ByteField("ProductMinorVersion", 0), 

533 LEShortField("ProductBuild", 0), 

534 LEThreeBytesField("res_ver", 0), 

535 ByteEnumField("NTLMRevisionCurrent", 0x0F, {0x0F: "v15"}), 

536 ] 

537 

538 

539# Sect 2.2.1.1 

540 

541 

542class NTLM_NEGOTIATE(_NTLM_VARIANT_Packet, NTLM_Header): 

543 name = "NTLM Negotiate" 

544 __slots__ = ["VARIANT"] 

545 MessageType = 1 

546 OFFSET = lambda pkt: ( 

547 32 

548 if ( 

549 pkt.VARIANT == NTLM_VARIANT.NT_OR_2000 

550 or (pkt.DomainNameBufferOffset or 40) <= 32 

551 ) 

552 else 40 

553 ) 

554 fields_desc = ( 

555 [ 

556 NTLM_Header, 

557 FlagsField("NegotiateFlags", 0, -32, _negotiateFlags), 

558 # DomainNameFields 

559 LEShortField("DomainNameLen", None), 

560 LEShortField("DomainNameMaxLen", None), 

561 LEIntField("DomainNameBufferOffset", None), 

562 # WorkstationFields 

563 LEShortField("WorkstationNameLen", None), 

564 LEShortField("WorkstationNameMaxLen", None), 

565 LEIntField("WorkstationNameBufferOffset", None), 

566 ] 

567 + [ 

568 # VERSION 

569 ConditionalField( 

570 # (not present on some old Windows versions. We use a heuristic) 

571 x, 

572 lambda pkt: pkt.VARIANT >= NTLM_VARIANT.XP_OR_2003 

573 and ( 

574 ( 

575 ( 

576 40 

577 if pkt.DomainNameBufferOffset is None 

578 else pkt.DomainNameBufferOffset or len(pkt.original or b"") 

579 ) 

580 > 32 

581 ) 

582 or pkt.fields.get(x.name, b"") 

583 ), 

584 ) 

585 for x in _NTLM_Version.fields_desc 

586 ] 

587 + [ 

588 # Payload 

589 _NTLMPayloadField( 

590 "Payload", 

591 OFFSET, 

592 [ 

593 _NTLMStrField("DomainName", b""), 

594 _NTLMStrField("WorkstationName", b""), 

595 ], 

596 ), 

597 ] 

598 ) 

599 

600 def post_build(self, pkt, pay): 

601 # type: (bytes, bytes) -> bytes 

602 return ( 

603 _NTLM_post_build( 

604 self, 

605 pkt, 

606 self.OFFSET(), 

607 { 

608 "DomainName": 16, 

609 "WorkstationName": 24, 

610 }, 

611 ) 

612 + pay 

613 ) 

614 

615 

616# Challenge 

617 

618 

619class Single_Host_Data(Packet): 

620 fields_desc = [ 

621 LEIntField("Size", None), 

622 LEIntField("Z4", 0), 

623 # "CustomData" guessed using LSAP_TOKEN_INFO_INTEGRITY. 

624 FlagsField( 

625 "Flags", 

626 0, 

627 -32, 

628 { 

629 0x00000001: "UAC-Restricted", 

630 }, 

631 ), 

632 LEIntEnumField( 

633 "TokenIL", 

634 0x00002000, 

635 { 

636 0x00000000: "Untrusted", 

637 0x00001000: "Low", 

638 0x00002000: "Medium", 

639 0x00003000: "High", 

640 0x00004000: "System", 

641 0x00005000: "Protected process", 

642 }, 

643 ), 

644 XStrFixedLenField("MachineID", b"", length=32), 

645 # KB 5068222 - still waiting for [MS-KILE] update (oct. 2025) 

646 ConditionalField( 

647 XStrFixedLenField("PermanentMachineID", None, length=32), 

648 lambda pkt: pkt.Size is None or pkt.Size > 48, 

649 ), 

650 ] 

651 

652 def post_build(self, pkt, pay): 

653 if self.Size is None: 

654 pkt = struct.pack("<I", len(pkt)) + pkt[4:] 

655 return pkt + pay 

656 

657 def default_payload_class(self, payload): 

658 return conf.padding_layer 

659 

660 

661class AV_PAIR(Packet): 

662 name = "NTLM AV Pair" 

663 fields_desc = [ 

664 LEShortEnumField( 

665 "AvId", 

666 0, 

667 { 

668 0x0000: "MsvAvEOL", 

669 0x0001: "MsvAvNbComputerName", 

670 0x0002: "MsvAvNbDomainName", 

671 0x0003: "MsvAvDnsComputerName", 

672 0x0004: "MsvAvDnsDomainName", 

673 0x0005: "MsvAvDnsTreeName", 

674 0x0006: "MsvAvFlags", 

675 0x0007: "MsvAvTimestamp", 

676 0x0008: "MsvAvSingleHost", 

677 0x0009: "MsvAvTargetName", 

678 0x000A: "MsvAvChannelBindings", 

679 }, 

680 ), 

681 FieldLenField("AvLen", None, length_of="Value", fmt="<H"), 

682 MultipleTypeField( 

683 [ 

684 ( 

685 LEIntEnumField( 

686 "Value", 

687 1, 

688 { 

689 0x0001: "constrained", 

690 0x0002: "MIC integrity", 

691 0x0004: "SPN from untrusted source", 

692 }, 

693 ), 

694 lambda pkt: pkt.AvId == 0x0006, 

695 ), 

696 ( 

697 UTCTimeField( 

698 "Value", 

699 None, 

700 epoch=[1601, 1, 1, 0, 0, 0], 

701 custom_scaling=1e7, 

702 fmt="<Q", 

703 ), 

704 lambda pkt: pkt.AvId == 0x0007, 

705 ), 

706 ( 

707 PacketField("Value", Single_Host_Data(), Single_Host_Data), 

708 lambda pkt: pkt.AvId == 0x0008, 

709 ), 

710 ( 

711 XStrLenField("Value", b"", length_from=lambda pkt: pkt.AvLen), 

712 lambda pkt: pkt.AvId == 0x000A, 

713 ), 

714 ], 

715 StrLenFieldUtf16("Value", b"", length_from=lambda pkt: pkt.AvLen), 

716 ), 

717 ] 

718 

719 def default_payload_class(self, payload): 

720 return conf.padding_layer 

721 

722 

723class NTLM_CHALLENGE(_NTLM_VARIANT_Packet, NTLM_Header): 

724 name = "NTLM Challenge" 

725 __slots__ = ["VARIANT"] 

726 MessageType = 2 

727 OFFSET = lambda pkt: ( 

728 48 

729 if ( 

730 pkt.VARIANT == NTLM_VARIANT.NT_OR_2000 

731 or (pkt.TargetInfoBufferOffset or 56) <= 48 

732 ) 

733 else 56 

734 ) 

735 fields_desc = ( 

736 [ 

737 NTLM_Header, 

738 # TargetNameFields 

739 LEShortField("TargetNameLen", None), 

740 LEShortField("TargetNameMaxLen", None), 

741 LEIntField("TargetNameBufferOffset", None), 

742 # 

743 FlagsField("NegotiateFlags", 0, -32, _negotiateFlags), 

744 XStrFixedLenField("ServerChallenge", None, length=8), 

745 XStrFixedLenField("Reserved", None, length=8), 

746 # TargetInfoFields 

747 LEShortField("TargetInfoLen", None), 

748 LEShortField("TargetInfoMaxLen", None), 

749 LEIntField("TargetInfoBufferOffset", None), 

750 ] 

751 + [ 

752 # VERSION 

753 ConditionalField( 

754 # (not present on some old Windows versions. We use a heuristic) 

755 x, 

756 lambda pkt: pkt.VARIANT >= NTLM_VARIANT.XP_OR_2003 

757 and ( 

758 ((pkt.TargetInfoBufferOffset or 56) > 40) 

759 or pkt.fields.get(x.name, b"") 

760 ), 

761 ) 

762 for x in _NTLM_Version.fields_desc 

763 ] 

764 + [ 

765 # Payload 

766 _NTLMPayloadField( 

767 "Payload", 

768 OFFSET, 

769 [ 

770 _NTLMStrField("TargetName", b""), 

771 PacketListField("TargetInfo", [AV_PAIR()], AV_PAIR), 

772 ], 

773 ), 

774 ] 

775 ) 

776 

777 def getAv(self, AvId): 

778 try: 

779 return next(x for x in self.TargetInfo if x.AvId == AvId) 

780 except (StopIteration, AttributeError): 

781 raise IndexError 

782 

783 def post_build(self, pkt, pay): 

784 # type: (bytes, bytes) -> bytes 

785 return ( 

786 _NTLM_post_build( 

787 self, 

788 pkt, 

789 self.OFFSET(), 

790 { 

791 "TargetName": 12, 

792 "TargetInfo": 40, 

793 }, 

794 ) 

795 + pay 

796 ) 

797 

798 

799# Authenticate 

800 

801 

802class LM_RESPONSE(Packet): 

803 fields_desc = [ 

804 StrFixedLenField("Response", b"", length=24), 

805 ] 

806 

807 

808class LMv2_RESPONSE(Packet): 

809 fields_desc = [ 

810 StrFixedLenField("Response", b"", length=16), 

811 StrFixedLenField("ChallengeFromClient", b"", length=8), 

812 ] 

813 

814 

815class NTLM_RESPONSE(Packet): 

816 fields_desc = [ 

817 StrFixedLenField("Response", b"", length=24), 

818 ] 

819 

820 

821class NTLMv2_CLIENT_CHALLENGE(Packet): 

822 fields_desc = [ 

823 ByteField("RespType", 1), 

824 ByteField("HiRespType", 1), 

825 LEShortField("Reserved1", 0), 

826 LEIntField("Reserved2", 0), 

827 UTCTimeField( 

828 "TimeStamp", None, fmt="<Q", epoch=[1601, 1, 1, 0, 0, 0], custom_scaling=1e7 

829 ), 

830 StrFixedLenField("ChallengeFromClient", b"12345678", length=8), 

831 LEIntField("Reserved3", 0), 

832 PacketListField("AvPairs", [AV_PAIR()], AV_PAIR), 

833 ] 

834 

835 def getAv(self, AvId): 

836 try: 

837 return next(x for x in self.AvPairs if x.AvId == AvId) 

838 except StopIteration: 

839 raise IndexError 

840 

841 

842class NTLMv2_RESPONSE(NTLMv2_CLIENT_CHALLENGE): 

843 fields_desc = [ 

844 XStrFixedLenField("NTProofStr", b"", length=16), 

845 NTLMv2_CLIENT_CHALLENGE, 

846 ] 

847 

848 def computeNTProofStr(self, ResponseKeyNT, ServerChallenge): 

849 """ 

850 Set temp to ConcatenationOf(Responserversion, HiResponserversion, 

851 Z(6), Time, ClientChallenge, Z(4), ServerName, Z(4)) 

852 Set NTProofStr to HMAC_MD5(ResponseKeyNT, 

853 ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge,temp)) 

854 

855 Remember ServerName = AvPairs 

856 """ 

857 Responserversion = b"\x01" 

858 HiResponserversion = b"\x01" 

859 

860 ServerName = b"".join(bytes(x) for x in self.AvPairs) 

861 temp = b"".join( 

862 [ 

863 Responserversion, 

864 HiResponserversion, 

865 b"\x00" * 6, 

866 struct.pack("<Q", self.TimeStamp), 

867 self.ChallengeFromClient, 

868 b"\x00" * 4, 

869 ServerName, 

870 # Final Z(4) is the EOL AvPair 

871 ] 

872 ) 

873 return HMAC_MD5(ResponseKeyNT, ServerChallenge + temp) 

874 

875 

876class NTLM_AUTHENTICATE(_NTLM_VARIANT_Packet, NTLM_Header): 

877 name = "NTLM Authenticate" 

878 __slots__ = ["VARIANT"] 

879 MessageType = 3 

880 NTLM_VERSION = 1 

881 OFFSET = lambda pkt: ( 

882 64 

883 if ( 

884 pkt.VARIANT == NTLM_VARIANT.NT_OR_2000 

885 or (pkt.DomainNameBufferOffset or 88) <= 64 

886 ) 

887 else ( 

888 72 

889 if pkt.VARIANT == NTLM_VARIANT.XP_OR_2003 

890 or ((pkt.DomainNameBufferOffset or 88) <= 72) 

891 else 88 

892 ) 

893 ) 

894 fields_desc = ( 

895 [ 

896 NTLM_Header, 

897 # LmChallengeResponseFields 

898 LEShortField("LmChallengeResponseLen", None), 

899 LEShortField("LmChallengeResponseMaxLen", None), 

900 LEIntField("LmChallengeResponseBufferOffset", None), 

901 # NtChallengeResponseFields 

902 LEShortField("NtChallengeResponseLen", None), 

903 LEShortField("NtChallengeResponseMaxLen", None), 

904 LEIntField("NtChallengeResponseBufferOffset", None), 

905 # DomainNameFields 

906 LEShortField("DomainNameLen", None), 

907 LEShortField("DomainNameMaxLen", None), 

908 LEIntField("DomainNameBufferOffset", None), 

909 # UserNameFields 

910 LEShortField("UserNameLen", None), 

911 LEShortField("UserNameMaxLen", None), 

912 LEIntField("UserNameBufferOffset", None), 

913 # WorkstationFields 

914 LEShortField("WorkstationLen", None), 

915 LEShortField("WorkstationMaxLen", None), 

916 LEIntField("WorkstationBufferOffset", None), 

917 # EncryptedRandomSessionKeyFields 

918 LEShortField("EncryptedRandomSessionKeyLen", None), 

919 LEShortField("EncryptedRandomSessionKeyMaxLen", None), 

920 LEIntField("EncryptedRandomSessionKeyBufferOffset", None), 

921 # NegotiateFlags 

922 FlagsField("NegotiateFlags", 0, -32, _negotiateFlags), 

923 # VERSION 

924 ] 

925 + [ 

926 ConditionalField( 

927 # (not present on some old Windows versions. We use a heuristic) 

928 x, 

929 lambda pkt: pkt.VARIANT >= NTLM_VARIANT.XP_OR_2003 

930 and ( 

931 ((pkt.DomainNameBufferOffset or 88) > 64) 

932 or pkt.fields.get(x.name, b"") 

933 ), 

934 ) 

935 for x in _NTLM_Version.fields_desc 

936 ] 

937 + [ 

938 # MIC 

939 ConditionalField( 

940 # (not present on some old Windows versions. We use a heuristic) 

941 XStrFixedLenField("MIC", b"", length=16), 

942 lambda pkt: pkt.VARIANT >= NTLM_VARIANT.RECENT 

943 and ( 

944 ((pkt.DomainNameBufferOffset or 88) > 72) 

945 or pkt.fields.get("MIC", b"") 

946 ), 

947 ), 

948 # Payload 

949 _NTLMPayloadField( 

950 "Payload", 

951 OFFSET, 

952 [ 

953 MultipleTypeField( 

954 [ 

955 ( 

956 PacketField( 

957 "LmChallengeResponse", 

958 LMv2_RESPONSE(), 

959 LMv2_RESPONSE, 

960 ), 

961 lambda pkt: pkt.NTLM_VERSION == 2, 

962 ) 

963 ], 

964 PacketField("LmChallengeResponse", LM_RESPONSE(), LM_RESPONSE), 

965 ), 

966 MultipleTypeField( 

967 [ 

968 ( 

969 PacketField( 

970 "NtChallengeResponse", 

971 NTLMv2_RESPONSE(), 

972 NTLMv2_RESPONSE, 

973 ), 

974 lambda pkt: pkt.NTLM_VERSION == 2, 

975 ) 

976 ], 

977 PacketField( 

978 "NtChallengeResponse", NTLM_RESPONSE(), NTLM_RESPONSE 

979 ), 

980 ), 

981 _NTLMStrField("DomainName", b""), 

982 _NTLMStrField("UserName", b""), 

983 _NTLMStrField("Workstation", b""), 

984 XStrField("EncryptedRandomSessionKey", b""), 

985 ], 

986 ), 

987 ] 

988 ) 

989 

990 def post_build(self, pkt, pay): 

991 # type: (bytes, bytes) -> bytes 

992 return ( 

993 _NTLM_post_build( 

994 self, 

995 pkt, 

996 self.OFFSET(), 

997 { 

998 "LmChallengeResponse": 12, 

999 "NtChallengeResponse": 20, 

1000 "DomainName": 28, 

1001 "UserName": 36, 

1002 "Workstation": 44, 

1003 "EncryptedRandomSessionKey": 52, 

1004 }, 

1005 ) 

1006 + pay 

1007 ) 

1008 

1009 def compute_mic(self, ExportedSessionKey, negotiate, challenge): 

1010 self.MIC = b"\x00" * 16 

1011 self.MIC = HMAC_MD5( 

1012 ExportedSessionKey, bytes(negotiate) + bytes(challenge) + bytes(self) 

1013 ) 

1014 

1015 

1016class NTLM_AUTHENTICATE_V2(NTLM_AUTHENTICATE): 

1017 NTLM_VERSION = 2 

1018 

1019 

1020def HTTP_ntlm_negotiate(ntlm_negotiate): 

1021 """Create an HTTP NTLM negotiate packet from an NTLM_NEGOTIATE message""" 

1022 assert isinstance(ntlm_negotiate, NTLM_NEGOTIATE) 

1023 from scapy.layers.http import HTTP, HTTPRequest 

1024 

1025 return HTTP() / HTTPRequest( 

1026 Authorization=b"NTLM " + bytes_base64(bytes(ntlm_negotiate)) 

1027 ) 

1028 

1029 

1030# Experimental - Reversed stuff 

1031 

1032# This is the GSSAPI NegoEX Exchange metadata blob. This is not documented 

1033# but described as an "opaque blob": this was reversed and everything is a 

1034# placeholder. 

1035 

1036 

1037class NEGOEX_EXCHANGE_NTLM_ITEM(ASN1_Packet): 

1038 ASN1_codec = ASN1_Codecs.BER 

1039 ASN1_root = ASN1F_SEQUENCE( 

1040 ASN1F_SEQUENCE( 

1041 ASN1F_SEQUENCE( 

1042 ASN1F_OID("oid", ""), 

1043 ASN1F_PRINTABLE_STRING("token", ""), 

1044 explicit_tag=0x31, 

1045 ), 

1046 explicit_tag=0x80, 

1047 ) 

1048 ) 

1049 

1050 

1051class NEGOEX_EXCHANGE_NTLM(ASN1_Packet): 

1052 """ 

1053 GSSAPI NegoEX Exchange metadata blob 

1054 This was reversed and may be meaningless 

1055 """ 

1056 

1057 ASN1_codec = ASN1_Codecs.BER 

1058 ASN1_root = ASN1F_SEQUENCE( 

1059 ASN1F_SEQUENCE( 

1060 ASN1F_SEQUENCE_OF("items", [], NEGOEX_EXCHANGE_NTLM_ITEM), implicit_tag=0xA0 

1061 ), 

1062 ) 

1063 

1064 

1065# Crypto - [MS-NLMP] 

1066 

1067 

1068def HMAC_MD5(key, data): 

1069 return Hmac_MD5(key=key).digest(data) 

1070 

1071 

1072def MD4le(x): 

1073 """ 

1074 MD4 over a string encoded as utf-16le 

1075 """ 

1076 return Hash_MD4().digest(x.encode("utf-16le")) 

1077 

1078 

1079def RC4Init(key): 

1080 """Alleged RC4""" 

1081 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms 

1082 

1083 try: 

1084 # cryptography > 43.0 

1085 from cryptography.hazmat.decrepit.ciphers import ( 

1086 algorithms as decrepit_algorithms, 

1087 ) 

1088 except ImportError: 

1089 decrepit_algorithms = algorithms 

1090 

1091 algorithm = decrepit_algorithms.ARC4(key) 

1092 cipher = Cipher(algorithm, mode=None) 

1093 encryptor = cipher.encryptor() 

1094 return encryptor 

1095 

1096 

1097def RC4(handle, data): 

1098 """The RC4 Encryption Algorithm""" 

1099 return handle.update(data) 

1100 

1101 

1102def RC4K(key, data): 

1103 """Indicates the encryption of data item D with the key K using the 

1104 RC4 algorithm. 

1105 """ 

1106 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms 

1107 

1108 try: 

1109 # cryptography > 43.0 

1110 from cryptography.hazmat.decrepit.ciphers import ( 

1111 algorithms as decrepit_algorithms, 

1112 ) 

1113 except ImportError: 

1114 decrepit_algorithms = algorithms 

1115 

1116 algorithm = decrepit_algorithms.ARC4(key) 

1117 cipher = Cipher(algorithm, mode=None) 

1118 encryptor = cipher.encryptor() 

1119 return encryptor.update(data) + encryptor.finalize() 

1120 

1121 

1122# sect 2.2.2.9 - With Extended Session Security 

1123 

1124 

1125class NTLMSSP_MESSAGE_SIGNATURE(Packet): 

1126 # [MS-RPCE] sect 2.2.2.9.1/2.2.2.9.2 

1127 fields_desc = [ 

1128 LEIntField("Version", 0x00000001), 

1129 XStrFixedLenField("Checksum", b"", length=8), 

1130 LEIntField("SeqNum", 0x00000000), 

1131 ] 

1132 

1133 def default_payload_class(self, payload): 

1134 return conf.padding_layer 

1135 

1136 

1137_GSSAPI_OIDS["1.3.6.1.4.1.311.2.2.10"] = NTLM_Header 

1138_GSSAPI_SIGNATURE_OIDS["1.3.6.1.4.1.311.2.2.10"] = NTLMSSP_MESSAGE_SIGNATURE 

1139 

1140 

1141# sect 3.3.2 

1142 

1143 

1144def NTOWFv2(Passwd, User, UserDom, HashNt=None): 

1145 """ 

1146 Computes the ResponseKeyNT (per [MS-NLMP] sect 3.3.2) 

1147 

1148 :param Passwd: the plain password 

1149 :param User: the username 

1150 :param UserDom: the domain name 

1151 :param HashNt: (out of spec) if you have the HashNt, use this and set 

1152 Passwd to None 

1153 """ 

1154 if HashNt is None: 

1155 HashNt = MD4le(Passwd) 

1156 return HMAC_MD5(HashNt, (User.upper() + UserDom).encode("utf-16le")) 

1157 

1158 

1159def NTLMv2_ComputeSessionBaseKey(ResponseKeyNT, NTProofStr): 

1160 return HMAC_MD5(ResponseKeyNT, NTProofStr) 

1161 

1162 

1163# sect 3.4.4.2 - With Extended Session Security 

1164 

1165 

1166def MAC(Handle, SigningKey, SeqNum, Message): 

1167 chksum = HMAC_MD5(SigningKey, struct.pack("<i", SeqNum) + Message)[:8] 

1168 if Handle: 

1169 chksum = RC4(Handle, chksum) 

1170 return NTLMSSP_MESSAGE_SIGNATURE( 

1171 Version=0x00000001, 

1172 Checksum=chksum, 

1173 SeqNum=SeqNum, 

1174 ) 

1175 

1176 

1177# sect 3.4.2 

1178 

1179 

1180def SIGN(Handle, SigningKey, SeqNum, Message): 

1181 # append? where is this used?! 

1182 return Message + MAC(Handle, SigningKey, SeqNum, Message) 

1183 

1184 

1185# sect 3.4.3 

1186 

1187 

1188def SEAL(Handle, SigningKey, SeqNum, Message): 

1189 """ 

1190 SEAL() according to [MS-NLMP] 

1191 """ 

1192 # this is unused. Use GSS_WrapEx 

1193 sealed_message = RC4(Handle, Message) 

1194 signature = MAC(Handle, SigningKey, SeqNum, Message) 

1195 return sealed_message, signature 

1196 

1197 

1198def UNSEAL(Handle, SigningKey, SeqNum, Message): 

1199 """ 

1200 UNSEAL() according to [MS-NLMP] 

1201 """ 

1202 # this is unused. Use GSS_UnwrapEx 

1203 unsealed_message = RC4(Handle, Message) 

1204 signature = MAC(Handle, SigningKey, SeqNum, Message) 

1205 return unsealed_message, signature 

1206 

1207 

1208# sect 3.4.5.2 

1209 

1210 

1211def SIGNKEY(NegFlg, ExportedSessionKey, Mode): 

1212 if NegFlg.NEGOTIATE_EXTENDED_SESSIONSECURITY: 

1213 if Mode == "Client": 

1214 return Hash_MD5().digest( 

1215 ExportedSessionKey 

1216 + b"session key to client-to-server signing key magic constant\x00" 

1217 ) 

1218 elif Mode == "Server": 

1219 return Hash_MD5().digest( 

1220 ExportedSessionKey 

1221 + b"session key to server-to-client signing key magic constant\x00" 

1222 ) 

1223 else: 

1224 raise ValueError("Unknown Mode") 

1225 else: 

1226 return None 

1227 

1228 

1229# sect 3.4.5.3 

1230 

1231 

1232def SEALKEY(NegFlg, ExportedSessionKey, Mode): 

1233 if NegFlg.NEGOTIATE_EXTENDED_SESSIONSECURITY: 

1234 if NegFlg.NEGOTIATE_128: 

1235 SealKey = ExportedSessionKey 

1236 elif NegFlg.NEGOTIATE_56: 

1237 SealKey = ExportedSessionKey[:7] 

1238 else: 

1239 SealKey = ExportedSessionKey[:5] 

1240 if Mode == "Client": 

1241 return Hash_MD5().digest( 

1242 SealKey 

1243 + b"session key to client-to-server sealing key magic constant\x00" 

1244 ) 

1245 elif Mode == "Server": 

1246 return Hash_MD5().digest( 

1247 SealKey 

1248 + b"session key to server-to-client sealing key magic constant\x00" 

1249 ) 

1250 else: 

1251 raise ValueError("Unknown Mode") 

1252 elif NegFlg.NEGOTIATE_LM_KEY: 

1253 if NegFlg.NEGOTIATE_56: 

1254 return ExportedSessionKey[:6] + b"\xa0" 

1255 else: 

1256 return ExportedSessionKey[:4] + b"\xe5\x38\xb0" 

1257 else: 

1258 return ExportedSessionKey 

1259 

1260 

1261# --- SSP 

1262 

1263 

1264class NTLMSSP(SSP): 

1265 """ 

1266 The NTLM SSP 

1267 

1268 Common arguments: 

1269 

1270 :param auth_level: One of DCE_C_AUTHN_LEVEL 

1271 :param USE_MIC: whether to use a MIC or not (default: True) 

1272 :param NTLM_VALUES: a dictionary used to override the following values 

1273 

1274 In case of a client:: 

1275 

1276 - NegotiateFlags 

1277 - ProductMajorVersion 

1278 - ProductMinorVersion 

1279 - ProductBuild 

1280 

1281 In case of a server:: 

1282 

1283 - NetbiosDomainName 

1284 - NetbiosComputerName 

1285 - DnsComputerName 

1286 - DnsDomainName (defaults to DOMAIN) 

1287 - DnsTreeName (defaults to DOMAIN) 

1288 - Flags 

1289 - Timestamp 

1290 

1291 Client-only arguments: 

1292 

1293 :param UPN: the UPN to use for NTLM auth. If no domain is specified, will 

1294 use the one provided by the server (domain in a domain, local 

1295 if without domain) 

1296 :param HASHNT: the password to use for NTLM auth 

1297 :param PASSWORD: the password to use for NTLM auth 

1298 

1299 Server-only arguments: 

1300 

1301 :param DOMAIN_FQDN: the domain FQDN (default: domain.local) 

1302 :param DOMAIN_NB_NAME: the domain Netbios name (default: strip DOMAIN_FQDN) 

1303 :param COMPUTER_NB_NAME: the server Netbios name (default: SRV) 

1304 :param COMPUTER_FQDN: the server FQDN 

1305 (default: <computer_nb_name>.<domain_fqdn>) 

1306 :param IDENTITIES: a dict {"username": <HashNT>} 

1307 Setting this value enables signature computation and 

1308 authenticates inbound users. 

1309 """ 

1310 

1311 auth_type = 0x0A 

1312 

1313 class STATE(SSP.STATE): 

1314 INIT = 1 

1315 CLI_SENT_NEGO = 2 

1316 CLI_SENT_AUTH = 3 

1317 SRV_SENT_CHAL = 4 

1318 

1319 class CONTEXT(SSP.CONTEXT): 

1320 __slots__ = [ 

1321 "SessionKey", 

1322 "ExportedSessionKey", 

1323 "IsAcceptor", 

1324 "SendSignKey", 

1325 "SendSealKey", 

1326 "RecvSignKey", 

1327 "RecvSealKey", 

1328 "SendSealHandle", 

1329 "RecvSealHandle", 

1330 "SendSeqNum", 

1331 "RecvSeqNum", 

1332 "neg_tok", 

1333 "chall_tok", 

1334 "ServerHostname", 

1335 "ServerDomain", 

1336 ] 

1337 

1338 def __init__(self, IsAcceptor, req_flags=None): 

1339 self.state = NTLMSSP.STATE.INIT 

1340 self.SessionKey = None 

1341 self.ExportedSessionKey = None 

1342 self.SendSignKey = None 

1343 self.SendSealKey = None 

1344 self.SendSealHandle = None 

1345 self.RecvSignKey = None 

1346 self.RecvSealKey = None 

1347 self.RecvSealHandle = None 

1348 self.SendSeqNum = 0 

1349 self.RecvSeqNum = 0 

1350 self.neg_tok = None 

1351 self.chall_tok = None 

1352 self.ServerHostname = None 

1353 self.ServerDomain = None 

1354 self.IsAcceptor = IsAcceptor 

1355 super(NTLMSSP.CONTEXT, self).__init__(req_flags=req_flags) 

1356 

1357 def clifailure(self): 

1358 self.__init__(self.IsAcceptor, req_flags=self.flags) 

1359 

1360 def __repr__(self): 

1361 return "NTLMSSP" 

1362 

1363 # [MS-NLMP] note <36>: "the maximum lifetime is 36 hours" (lol, Kerberos has 5min) 

1364 NTLM_MaxLifetime = 36 * 3600 

1365 

1366 def __init__( 

1367 self, 

1368 UPN=None, 

1369 HASHNT=None, 

1370 PASSWORD=None, 

1371 USE_MIC=True, 

1372 VARIANT: NTLM_VARIANT = NTLM_VARIANT.RECENT, 

1373 NTLM_VALUES={}, 

1374 DOMAIN_FQDN=None, 

1375 DOMAIN_NB_NAME=None, 

1376 COMPUTER_NB_NAME=None, 

1377 COMPUTER_FQDN=None, 

1378 IDENTITIES=None, 

1379 DO_NOT_CHECK_LOGIN=False, 

1380 SERVER_CHALLENGE=None, 

1381 **kwargs, 

1382 ): 

1383 self.UPN = UPN 

1384 if HASHNT is None and PASSWORD is not None: 

1385 HASHNT = MD4le(PASSWORD) 

1386 self.HASHNT = HASHNT 

1387 self.VARIANT = VARIANT 

1388 if self.VARIANT != NTLM_VARIANT.RECENT: 

1389 log_runtime.warning( 

1390 "VARIANT != NTLM_VARIANT.RECENT. You shouldn't touch this !" 

1391 ) 

1392 self.USE_MIC = False 

1393 else: 

1394 self.USE_MIC = USE_MIC 

1395 self.NTLM_VALUES = NTLM_VALUES 

1396 if UPN is not None: 

1397 # Populate values used only in server mode. 

1398 from scapy.layers.kerberos import _parse_upn 

1399 

1400 try: 

1401 user, realm = _parse_upn(UPN) 

1402 if DOMAIN_FQDN is None: 

1403 DOMAIN_FQDN = realm 

1404 if COMPUTER_NB_NAME is None: 

1405 COMPUTER_NB_NAME = user 

1406 except ValueError: 

1407 pass 

1408 

1409 # Compute various netbios/fqdn names 

1410 self.DOMAIN_FQDN = DOMAIN_FQDN or "domain.local" 

1411 self.DOMAIN_NB_NAME = ( 

1412 DOMAIN_NB_NAME or self.DOMAIN_FQDN.split(".")[0].upper()[:15] 

1413 ) 

1414 self.COMPUTER_NB_NAME = COMPUTER_NB_NAME or "WIN10" 

1415 self.COMPUTER_FQDN = COMPUTER_FQDN or ( 

1416 self.COMPUTER_NB_NAME.lower() + "." + self.DOMAIN_FQDN 

1417 ) 

1418 

1419 if IDENTITIES: 

1420 self.IDENTITIES = { 

1421 # Windows usernames are case insensitive 

1422 user.upper(): hashnt 

1423 for user, hashnt in IDENTITIES.items() 

1424 } 

1425 else: 

1426 self.IDENTITIES = IDENTITIES 

1427 

1428 self.DO_NOT_CHECK_LOGIN = DO_NOT_CHECK_LOGIN 

1429 self.SERVER_CHALLENGE = SERVER_CHALLENGE 

1430 super(NTLMSSP, self).__init__(**kwargs) 

1431 

1432 def LegsAmount(self, Context: CONTEXT): 

1433 return 3 

1434 

1435 def GSS_Inquire_names_for_mech(self): 

1436 return ["1.3.6.1.4.1.311.2.2.10"] 

1437 

1438 def GSS_GetMICEx(self, Context, msgs, qop_req=0): 

1439 """ 

1440 [MS-NLMP] sect 3.4.8 

1441 """ 

1442 # Concatenate the ToSign 

1443 ToSign = b"".join(x.data for x in msgs if x.sign) 

1444 sig = MAC( 

1445 Context.SendSealHandle, 

1446 Context.SendSignKey, 

1447 Context.SendSeqNum, 

1448 ToSign, 

1449 ) 

1450 Context.SendSeqNum += 1 

1451 return sig 

1452 

1453 def GSS_VerifyMICEx(self, Context, msgs, signature): 

1454 """ 

1455 [MS-NLMP] sect 3.4.9 

1456 """ 

1457 Context.RecvSeqNum = signature.SeqNum 

1458 # Concatenate the ToSign 

1459 ToSign = b"".join(x.data for x in msgs if x.sign) 

1460 sig = MAC( 

1461 Context.RecvSealHandle, 

1462 Context.RecvSignKey, 

1463 Context.RecvSeqNum, 

1464 ToSign, 

1465 ) 

1466 if sig.Checksum != signature.Checksum: 

1467 raise ValueError("ERROR: Checksums don't match") 

1468 

1469 def GSS_WrapEx(self, Context, msgs, qop_req=0): 

1470 """ 

1471 [MS-NLMP] sect 3.4.6 

1472 """ 

1473 msgs_cpy = copy.deepcopy(msgs) # Keep copy for signature 

1474 # Encrypt 

1475 for msg in msgs: 

1476 if msg.conf_req_flag: 

1477 msg.data = RC4(Context.SendSealHandle, msg.data) 

1478 # Sign 

1479 sig = self.GSS_GetMICEx(Context, msgs_cpy, qop_req=qop_req) 

1480 return ( 

1481 msgs, 

1482 sig, 

1483 ) 

1484 

1485 def GSS_UnwrapEx(self, Context, msgs, signature): 

1486 """ 

1487 [MS-NLMP] sect 3.4.7 

1488 """ 

1489 # Decrypt 

1490 for msg in msgs: 

1491 if msg.conf_req_flag: 

1492 msg.data = RC4(Context.RecvSealHandle, msg.data) 

1493 # Check signature 

1494 self.GSS_VerifyMICEx(Context, msgs, signature) 

1495 return msgs 

1496 

1497 def SupportsMechListMIC(self): 

1498 if not self.USE_MIC: 

1499 # RFC 4178 

1500 # "If the mechanism selected by the negotiation does not support integrity 

1501 # protection, then no mechlistMIC token is used." 

1502 return False 

1503 if self.DO_NOT_CHECK_LOGIN: 

1504 # In this mode, we won't negotiate any credentials. 

1505 return False 

1506 return True 

1507 

1508 def GetMechListMIC(self, Context, input): 

1509 # [MS-SPNG] 

1510 # "When NTLM is negotiated, the SPNG server MUST set OriginalHandle to 

1511 # ServerHandle before generating the mechListMIC, then set ServerHandle to 

1512 # OriginalHandle after generating the mechListMIC." 

1513 OriginalHandle = Context.SendSealHandle 

1514 Context.SendSealHandle = RC4Init(Context.SendSealKey) 

1515 try: 

1516 return super(NTLMSSP, self).GetMechListMIC(Context, input) 

1517 finally: 

1518 Context.SendSealHandle = OriginalHandle 

1519 

1520 def VerifyMechListMIC(self, Context, otherMIC, input): 

1521 # [MS-SPNG] 

1522 # "the SPNEGO Extension server MUST set OriginalHandle to ClientHandle before 

1523 # validating the mechListMIC and then set ClientHandle to OriginalHandle after 

1524 # validating the mechListMIC." 

1525 OriginalHandle = Context.RecvSealHandle 

1526 Context.RecvSealHandle = RC4Init(Context.RecvSealKey) 

1527 try: 

1528 return super(NTLMSSP, self).VerifyMechListMIC(Context, otherMIC, input) 

1529 finally: 

1530 Context.RecvSealHandle = OriginalHandle 

1531 

1532 def GSS_Init_sec_context( 

1533 self, 

1534 Context: CONTEXT, 

1535 input_token=None, 

1536 target_name: Optional[str] = None, 

1537 req_flags: Optional[GSS_C_FLAGS] = None, 

1538 chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, 

1539 ): 

1540 if Context is None: 

1541 Context = self.CONTEXT(False, req_flags=req_flags) 

1542 

1543 if Context.state == self.STATE.INIT: 

1544 # Client: negotiate 

1545 # Create a default token 

1546 tok = NTLM_NEGOTIATE( 

1547 VARIANT=self.VARIANT, 

1548 NegotiateFlags="+".join( 

1549 [ 

1550 "NEGOTIATE_UNICODE", 

1551 "REQUEST_TARGET", 

1552 "NEGOTIATE_NTLM", 

1553 "NEGOTIATE_ALWAYS_SIGN", 

1554 "TARGET_TYPE_DOMAIN", 

1555 "NEGOTIATE_EXTENDED_SESSIONSECURITY", 

1556 "NEGOTIATE_TARGET_INFO", 

1557 "NEGOTIATE_128", 

1558 "NEGOTIATE_56", 

1559 ] 

1560 + ( 

1561 ["NEGOTIATE_VERSION"] 

1562 if self.VARIANT >= NTLM_VARIANT.XP_OR_2003 

1563 else [] 

1564 ) 

1565 + ( 

1566 [ 

1567 "NEGOTIATE_KEY_EXCH", 

1568 ] 

1569 if Context.flags 

1570 & (GSS_C_FLAGS.GSS_C_INTEG_FLAG | GSS_C_FLAGS.GSS_C_CONF_FLAG) 

1571 else [] 

1572 ) 

1573 + ( 

1574 [ 

1575 "NEGOTIATE_SIGN", 

1576 ] 

1577 if Context.flags & GSS_C_FLAGS.GSS_C_INTEG_FLAG 

1578 else [] 

1579 ) 

1580 + ( 

1581 [ 

1582 "NEGOTIATE_SEAL", 

1583 ] 

1584 if Context.flags & GSS_C_FLAGS.GSS_C_CONF_FLAG 

1585 else [] 

1586 ) 

1587 + ( 

1588 [ 

1589 "NEGOTIATE_IDENTIFY", 

1590 ] 

1591 if Context.flags & GSS_C_FLAGS.GSS_C_IDENTIFY_FLAG 

1592 else [] 

1593 ) 

1594 ), 

1595 ProductMajorVersion=10, 

1596 ProductMinorVersion=0, 

1597 ProductBuild=19041, 

1598 ) 

1599 if self.NTLM_VALUES: 

1600 # Update that token with the customs one 

1601 for key in [ 

1602 "NegotiateFlags", 

1603 "ProductMajorVersion", 

1604 "ProductMinorVersion", 

1605 "ProductBuild", 

1606 ]: 

1607 if key in self.NTLM_VALUES: 

1608 setattr(tok, key, self.NTLM_VALUES[key]) 

1609 Context.neg_tok = tok 

1610 Context.SessionKey = None # Reset signing (if previous auth failed) 

1611 Context.state = self.STATE.CLI_SENT_NEGO 

1612 return Context, tok, GSS_S_CONTINUE_NEEDED 

1613 elif Context.state == self.STATE.CLI_SENT_NEGO: 

1614 # Client: auth (token=challenge) 

1615 chall_tok = input_token 

1616 if self.UPN is None or self.HASHNT is None: 

1617 raise ValueError( 

1618 "Must provide a 'UPN' and a 'HASHNT' or 'PASSWORD' when " 

1619 "running in standalone !" 

1620 ) 

1621 

1622 from scapy.layers.kerberos import _parse_upn 

1623 

1624 # Check token sanity 

1625 if not chall_tok or NTLM_CHALLENGE not in chall_tok: 

1626 log_runtime.debug("NTLMSSP: Unexpected token. Expected NTLM Challenge") 

1627 return Context, None, GSS_S_DEFECTIVE_TOKEN 

1628 

1629 # Some information from the CHALLENGE are stored 

1630 try: 

1631 Context.ServerHostname = chall_tok.getAv(0x0001).Value 

1632 except IndexError: 

1633 pass 

1634 try: 

1635 Context.ServerDomain = chall_tok.getAv(0x0002).Value 

1636 except IndexError: 

1637 pass 

1638 try: 

1639 # the server SHOULD set the timestamp in the CHALLENGE_MESSAGE 

1640 ServerTimestamp = chall_tok.getAv(0x0007).Value 

1641 ServerTime = (ServerTimestamp / 1e7) - 11644473600 

1642 

1643 if abs(ServerTime - time.time()) >= NTLMSSP.NTLM_MaxLifetime: 

1644 log_runtime.warning( 

1645 "Server and Client times are off by more than 36h !" 

1646 ) 

1647 # We could error here, but we don't. 

1648 except IndexError: 

1649 pass 

1650 

1651 # Initialize a default token 

1652 tok = NTLM_AUTHENTICATE_V2( 

1653 VARIANT=self.VARIANT, 

1654 NegotiateFlags=chall_tok.NegotiateFlags, 

1655 ProductMajorVersion=10, 

1656 ProductMinorVersion=0, 

1657 ProductBuild=19041, 

1658 ) 

1659 tok.LmChallengeResponse = LMv2_RESPONSE() 

1660 

1661 # Populate the token 

1662 # 1. Set username 

1663 try: 

1664 tok.UserName, realm = _parse_upn(self.UPN) 

1665 except ValueError: 

1666 tok.UserName, realm = self.UPN, Context.ServerDomain 

1667 

1668 # 2. Set domain name 

1669 if realm is None: 

1670 log_runtime.warning( 

1671 "No realm specified in UPN, nor provided by server." 

1672 ) 

1673 tok.DomainName = self.DOMAIN_FQDN 

1674 else: 

1675 tok.DomainName = realm 

1676 

1677 # 3. Set workstation name 

1678 tok.Workstation = self.COMPUTER_NB_NAME 

1679 

1680 # 4. Create and calculate the ChallengeResponse 

1681 # 4.1 Build the payload 

1682 cr = tok.NtChallengeResponse = NTLMv2_RESPONSE( 

1683 ChallengeFromClient=os.urandom(8), 

1684 ) 

1685 cr.TimeStamp = int((time.time() + 11644473600) * 1e7) 

1686 cr.AvPairs = ( 

1687 # Repeat AvPairs from the server 

1688 chall_tok.TargetInfo[:-1] 

1689 + ( 

1690 [ 

1691 AV_PAIR(AvId="MsvAvFlags", Value="MIC integrity"), 

1692 ] 

1693 if self.USE_MIC 

1694 else [] 

1695 ) 

1696 + [ 

1697 AV_PAIR( 

1698 AvId="MsvAvSingleHost", 

1699 Value=Single_Host_Data( 

1700 MachineID=os.urandom(32), 

1701 PermanentMachineID=os.urandom(32), 

1702 ), 

1703 ), 

1704 ] 

1705 + ( 

1706 [ 

1707 AV_PAIR( 

1708 # [MS-NLMP] sect 2.2.2.1 refers to RFC 4121 sect 4.1.1.2 

1709 # "The Bnd field contains the MD5 hash of channel bindings" 

1710 AvId="MsvAvChannelBindings", 

1711 Value=chan_bindings.digestMD5(), 

1712 ), 

1713 ] 

1714 if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS 

1715 else [] 

1716 ) 

1717 + [ 

1718 AV_PAIR( 

1719 AvId="MsvAvTargetName", 

1720 Value=target_name or ("host/" + Context.ServerHostname), 

1721 ), 

1722 AV_PAIR(AvId="MsvAvEOL"), 

1723 ] 

1724 ) 

1725 if self.NTLM_VALUES: 

1726 # Update that token with the customs one 

1727 for key in [ 

1728 "NegotiateFlags", 

1729 "ProductMajorVersion", 

1730 "ProductMinorVersion", 

1731 "ProductBuild", 

1732 ]: 

1733 if key in self.NTLM_VALUES: 

1734 setattr(tok, key, self.NTLM_VALUES[key]) 

1735 

1736 # 4.2 Compute the ResponseKeyNT 

1737 ResponseKeyNT = NTOWFv2( 

1738 None, 

1739 tok.UserName, 

1740 tok.DomainName, 

1741 HashNt=self.HASHNT, 

1742 ) 

1743 

1744 # 4.3 Compute the NTProofStr 

1745 cr.NTProofStr = cr.computeNTProofStr( 

1746 ResponseKeyNT, 

1747 chall_tok.ServerChallenge, 

1748 ) 

1749 

1750 # 4.4 Compute the Session Key 

1751 SessionBaseKey = NTLMv2_ComputeSessionBaseKey(ResponseKeyNT, cr.NTProofStr) 

1752 KeyExchangeKey = SessionBaseKey # Only true for NTLMv2 

1753 if chall_tok.NegotiateFlags.NEGOTIATE_KEY_EXCH: 

1754 ExportedSessionKey = os.urandom(16) 

1755 tok.EncryptedRandomSessionKey = RC4K( 

1756 KeyExchangeKey, 

1757 ExportedSessionKey, 

1758 ) 

1759 else: 

1760 ExportedSessionKey = KeyExchangeKey 

1761 

1762 # 4.5 Compute the MIC 

1763 if self.USE_MIC: 

1764 tok.compute_mic(ExportedSessionKey, Context.neg_tok, chall_tok) 

1765 

1766 # 5. Perform key computations 

1767 Context.ExportedSessionKey = ExportedSessionKey 

1768 # [MS-SMB] 3.2.5.3 

1769 Context.SessionKey = Context.ExportedSessionKey 

1770 # Compute NTLM keys 

1771 Context.SendSignKey = SIGNKEY( 

1772 tok.NegotiateFlags, ExportedSessionKey, "Client" 

1773 ) 

1774 Context.SendSealKey = SEALKEY( 

1775 tok.NegotiateFlags, ExportedSessionKey, "Client" 

1776 ) 

1777 Context.SendSealHandle = RC4Init(Context.SendSealKey) 

1778 Context.RecvSignKey = SIGNKEY( 

1779 tok.NegotiateFlags, ExportedSessionKey, "Server" 

1780 ) 

1781 Context.RecvSealKey = SEALKEY( 

1782 tok.NegotiateFlags, ExportedSessionKey, "Server" 

1783 ) 

1784 Context.RecvSealHandle = RC4Init(Context.RecvSealKey) 

1785 

1786 # Update the state 

1787 Context.state = self.STATE.CLI_SENT_AUTH 

1788 

1789 return Context, tok, GSS_S_COMPLETE 

1790 elif Context.state == self.STATE.CLI_SENT_AUTH: 

1791 if input_token: 

1792 # what is that? 

1793 status = GSS_S_DEFECTIVE_TOKEN 

1794 else: 

1795 status = GSS_S_COMPLETE 

1796 return Context, None, status 

1797 else: 

1798 raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state)) 

1799 

1800 def GSS_Accept_sec_context( 

1801 self, 

1802 Context: CONTEXT, 

1803 input_token=None, 

1804 req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS, 

1805 chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, 

1806 ): 

1807 if Context is None: 

1808 Context = self.CONTEXT(IsAcceptor=True, req_flags=req_flags) 

1809 

1810 if Context.state == self.STATE.INIT: 

1811 # Server: challenge (input_token=negotiate) 

1812 nego_tok = input_token 

1813 if not nego_tok or NTLM_NEGOTIATE not in nego_tok: 

1814 log_runtime.debug("NTLMSSP: Unexpected token. Expected NTLM Negotiate") 

1815 return Context, None, GSS_S_DEFECTIVE_TOKEN 

1816 

1817 # Build the challenge token 

1818 currentTime = (time.time() + 11644473600) * 1e7 

1819 tok = NTLM_CHALLENGE( 

1820 VARIANT=self.VARIANT, 

1821 ServerChallenge=self.SERVER_CHALLENGE or os.urandom(8), 

1822 NegotiateFlags="+".join( 

1823 [ 

1824 "NEGOTIATE_UNICODE", 

1825 "REQUEST_TARGET", 

1826 "NEGOTIATE_NTLM", 

1827 "NEGOTIATE_ALWAYS_SIGN", 

1828 "NEGOTIATE_EXTENDED_SESSIONSECURITY", 

1829 "NEGOTIATE_TARGET_INFO", 

1830 "TARGET_TYPE_DOMAIN", 

1831 "NEGOTIATE_128", 

1832 "NEGOTIATE_KEY_EXCH", 

1833 "NEGOTIATE_56", 

1834 ] 

1835 + ( 

1836 ["NEGOTIATE_VERSION"] 

1837 if self.VARIANT >= NTLM_VARIANT.XP_OR_2003 

1838 else [] 

1839 ) 

1840 + ( 

1841 ["NEGOTIATE_SIGN"] 

1842 if nego_tok.NegotiateFlags.NEGOTIATE_SIGN 

1843 else [] 

1844 ) 

1845 + ( 

1846 ["NEGOTIATE_SEAL"] 

1847 if nego_tok.NegotiateFlags.NEGOTIATE_SEAL 

1848 else [] 

1849 ) 

1850 ), 

1851 ProductMajorVersion=10, 

1852 ProductMinorVersion=0, 

1853 Payload=[ 

1854 ("TargetName", ""), 

1855 ( 

1856 "TargetInfo", 

1857 [ 

1858 # MsvAvNbComputerName 

1859 AV_PAIR(AvId=1, Value=self.COMPUTER_NB_NAME), 

1860 # MsvAvNbDomainName 

1861 AV_PAIR(AvId=2, Value=self.DOMAIN_NB_NAME), 

1862 # MsvAvDnsComputerName 

1863 AV_PAIR(AvId=3, Value=self.COMPUTER_FQDN), 

1864 # MsvAvDnsDomainName 

1865 AV_PAIR(AvId=4, Value=self.DOMAIN_FQDN), 

1866 # MsvAvDnsTreeName 

1867 AV_PAIR(AvId=5, Value=self.DOMAIN_FQDN), 

1868 # MsvAvTimestamp 

1869 AV_PAIR(AvId=7, Value=currentTime), 

1870 # MsvAvEOL 

1871 AV_PAIR(AvId=0), 

1872 ], 

1873 ), 

1874 ], 

1875 ) 

1876 if self.NTLM_VALUES: 

1877 # Update that token with the customs one 

1878 for key in [ 

1879 "ServerChallenge", 

1880 "NegotiateFlags", 

1881 "ProductMajorVersion", 

1882 "ProductMinorVersion", 

1883 "TargetName", 

1884 ]: 

1885 if key in self.NTLM_VALUES: 

1886 setattr(tok, key, self.NTLM_VALUES[key]) 

1887 avpairs = {x.AvId: x.Value for x in tok.TargetInfo} 

1888 tok.TargetInfo = [ 

1889 AV_PAIR(AvId=i, Value=self.NTLM_VALUES.get(x, avpairs[i])) 

1890 for (i, x) in [ 

1891 (2, "NetbiosDomainName"), 

1892 (1, "NetbiosComputerName"), 

1893 (4, "DnsDomainName"), 

1894 (3, "DnsComputerName"), 

1895 (5, "DnsTreeName"), 

1896 (6, "Flags"), 

1897 (7, "Timestamp"), 

1898 (0, None), 

1899 ] 

1900 if ((x in self.NTLM_VALUES) or (i in avpairs)) 

1901 and self.NTLM_VALUES.get(x, True) is not None 

1902 ] 

1903 

1904 # Store for next step 

1905 Context.chall_tok = tok 

1906 

1907 # Update the state 

1908 Context.state = self.STATE.SRV_SENT_CHAL 

1909 

1910 return Context, tok, GSS_S_CONTINUE_NEEDED 

1911 elif Context.state == self.STATE.SRV_SENT_CHAL: 

1912 # server: OK or challenge again (input_token=auth) 

1913 auth_tok = input_token 

1914 

1915 if not auth_tok or NTLM_AUTHENTICATE_V2 not in auth_tok: 

1916 log_runtime.debug( 

1917 "NTLMSSP: Unexpected token. Expected NTLM Authenticate v2" 

1918 ) 

1919 return Context, None, GSS_S_DEFECTIVE_TOKEN 

1920 

1921 if self.DO_NOT_CHECK_LOGIN: 

1922 # Just trust me bro. Typically used in "guest" mode. 

1923 return Context, None, GSS_S_COMPLETE 

1924 

1925 # Compute the session key 

1926 SessionBaseKey = self._getSessionBaseKey(Context, auth_tok) 

1927 if SessionBaseKey: 

1928 # [MS-NLMP] sect 3.2.5.1.2 

1929 KeyExchangeKey = SessionBaseKey # Only true for NTLMv2 

1930 if auth_tok.NegotiateFlags.NEGOTIATE_KEY_EXCH: 

1931 try: 

1932 EncryptedRandomSessionKey = auth_tok.EncryptedRandomSessionKey 

1933 except AttributeError: 

1934 # No EncryptedRandomSessionKey. libcurl for instance 

1935 # hmm. this looks bad 

1936 EncryptedRandomSessionKey = b"\x00" * 16 

1937 ExportedSessionKey = RC4K(KeyExchangeKey, EncryptedRandomSessionKey) 

1938 else: 

1939 ExportedSessionKey = KeyExchangeKey 

1940 Context.ExportedSessionKey = ExportedSessionKey 

1941 # [MS-SMB] 3.2.5.3 

1942 Context.SessionKey = Context.ExportedSessionKey 

1943 

1944 # Check the timestamp 

1945 try: 

1946 ClientTimestamp = auth_tok.NtChallengeResponse.getAv(0x0007).Value 

1947 ClientTime = (ClientTimestamp / 1e7) - 11644473600 

1948 

1949 if abs(ClientTime - time.time()) >= NTLMSSP.NTLM_MaxLifetime: 

1950 log_runtime.warning( 

1951 "Server and Client times are off by more than 36h !" 

1952 ) 

1953 # We could error here, but we don't. 

1954 except IndexError: 

1955 pass 

1956 

1957 # Check the channel bindings 

1958 if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS: 

1959 try: 

1960 Bnd = auth_tok.NtChallengeResponse.getAv(0x000A).Value 

1961 if Bnd != chan_bindings.digestMD5(): 

1962 # Bad channel bindings 

1963 return Context, None, GSS_S_BAD_BINDINGS 

1964 except IndexError: 

1965 if GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS not in req_flags: 

1966 # Uhoh, we required channel bindings 

1967 return Context, None, GSS_S_BAD_BINDINGS 

1968 

1969 if Context.SessionKey: 

1970 # Compute NTLM keys 

1971 Context.SendSignKey = SIGNKEY( 

1972 auth_tok.NegotiateFlags, ExportedSessionKey, "Server" 

1973 ) 

1974 Context.SendSealKey = SEALKEY( 

1975 auth_tok.NegotiateFlags, ExportedSessionKey, "Server" 

1976 ) 

1977 Context.SendSealHandle = RC4Init(Context.SendSealKey) 

1978 Context.RecvSignKey = SIGNKEY( 

1979 auth_tok.NegotiateFlags, ExportedSessionKey, "Client" 

1980 ) 

1981 Context.RecvSealKey = SEALKEY( 

1982 auth_tok.NegotiateFlags, ExportedSessionKey, "Client" 

1983 ) 

1984 Context.RecvSealHandle = RC4Init(Context.RecvSealKey) 

1985 

1986 # Check the NTProofStr 

1987 if self._checkLogin(Context, auth_tok): 

1988 # Set negotiated flags 

1989 if auth_tok.NegotiateFlags.NEGOTIATE_SIGN: 

1990 Context.flags |= GSS_C_FLAGS.GSS_C_INTEG_FLAG 

1991 if auth_tok.NegotiateFlags.NEGOTIATE_SEAL: 

1992 Context.flags |= GSS_C_FLAGS.GSS_C_CONF_FLAG 

1993 return Context, None, GSS_S_COMPLETE 

1994 

1995 # Bad NTProofStr or unknown user 

1996 Context.SessionKey = None 

1997 Context.state = self.STATE.INIT 

1998 return Context, None, GSS_S_DEFECTIVE_CREDENTIAL 

1999 else: 

2000 raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state)) 

2001 

2002 def MaximumSignatureLength(self, Context: CONTEXT): 

2003 """ 

2004 Returns the Maximum Signature length. 

2005 

2006 This will be used in auth_len in DceRpc5, and is necessary for 

2007 PFC_SUPPORT_HEADER_SIGN to work properly. 

2008 """ 

2009 return 16 # len(NTLMSSP_MESSAGE_SIGNATURE()) 

2010 

2011 def GSS_Passive(self, Context: CONTEXT, token=None, req_flags=None): 

2012 if Context is None: 

2013 Context = self.CONTEXT(True) 

2014 Context.passive = True 

2015 

2016 # We capture the Negotiate, Challenge, then call the server's auth handling 

2017 # and discard the output. 

2018 

2019 if Context.state == self.STATE.INIT: 

2020 if not token or NTLM_NEGOTIATE not in token: 

2021 log_runtime.warning("NTLMSSP: Expected NTLM Negotiate") 

2022 return None, GSS_S_DEFECTIVE_TOKEN 

2023 Context.neg_tok = token 

2024 Context.state = self.STATE.CLI_SENT_NEGO 

2025 return Context, GSS_S_CONTINUE_NEEDED 

2026 elif Context.state == self.STATE.CLI_SENT_NEGO: 

2027 if not token or NTLM_CHALLENGE not in token: 

2028 log_runtime.warning("NTLMSSP: Expected NTLM Challenge") 

2029 return None, GSS_S_DEFECTIVE_TOKEN 

2030 Context.chall_tok = token 

2031 Context.state = self.STATE.SRV_SENT_CHAL 

2032 return Context, GSS_S_CONTINUE_NEEDED 

2033 elif Context.state == self.STATE.SRV_SENT_CHAL: 

2034 if not token or NTLM_AUTHENTICATE_V2 not in token: 

2035 log_runtime.warning("NTLMSSP: Expected NTLM Authenticate") 

2036 return None, GSS_S_DEFECTIVE_TOKEN 

2037 Context, _, status = self.GSS_Accept_sec_context(Context, token) 

2038 if status != GSS_S_COMPLETE: 

2039 log_runtime.info("NTLMSSP: auth failed.") 

2040 Context.state = self.STATE.INIT 

2041 return Context, status 

2042 else: 

2043 raise ValueError("NTLMSSP: unexpected state %s" % repr(Context.state)) 

2044 

2045 def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False): 

2046 if Context.IsAcceptor is not IsAcceptor: 

2047 return 

2048 # Swap everything 

2049 Context.SendSignKey, Context.RecvSignKey = ( 

2050 Context.RecvSignKey, 

2051 Context.SendSignKey, 

2052 ) 

2053 Context.SendSealKey, Context.RecvSealKey = ( 

2054 Context.RecvSealKey, 

2055 Context.SendSealKey, 

2056 ) 

2057 Context.SendSealHandle, Context.RecvSealHandle = ( 

2058 Context.RecvSealHandle, 

2059 Context.SendSealHandle, 

2060 ) 

2061 Context.SendSeqNum, Context.RecvSeqNum = Context.RecvSeqNum, Context.SendSeqNum 

2062 Context.IsAcceptor = not Context.IsAcceptor 

2063 

2064 def _getSessionBaseKey(self, Context, auth_tok): 

2065 """ 

2066 Function that returns the SessionBaseKey from the ntlm Authenticate. 

2067 """ 

2068 try: 

2069 # Windows usernames are case insensitive 

2070 username = auth_tok.UserName.upper() 

2071 except AttributeError: 

2072 username = None 

2073 try: 

2074 domain = auth_tok.DomainName 

2075 except AttributeError: 

2076 domain = "" 

2077 if self.IDENTITIES and username in self.IDENTITIES: 

2078 ResponseKeyNT = NTOWFv2( 

2079 None, 

2080 username, 

2081 domain, 

2082 HashNt=self.IDENTITIES[username], 

2083 ) 

2084 return NTLMv2_ComputeSessionBaseKey( 

2085 ResponseKeyNT, 

2086 auth_tok.NtChallengeResponse.NTProofStr, 

2087 ) 

2088 elif self.IDENTITIES: 

2089 log_runtime.debug("NTLMSSP: Bad credentials for %s" % username) 

2090 return None 

2091 

2092 def _checkLogin(self, Context, auth_tok): 

2093 """ 

2094 Function that checks the validity of an authentication. 

2095 

2096 Overwrite and return True to bypass. 

2097 """ 

2098 try: 

2099 # Windows usernames are case insensitive 

2100 username = auth_tok.UserName.upper() 

2101 except AttributeError: 

2102 username = None 

2103 try: 

2104 domain = auth_tok.DomainName 

2105 except AttributeError: 

2106 domain = "" 

2107 if username in self.IDENTITIES: 

2108 ResponseKeyNT = NTOWFv2( 

2109 None, 

2110 username, 

2111 domain, 

2112 HashNt=self.IDENTITIES[username], 

2113 ) 

2114 NTProofStr = auth_tok.NtChallengeResponse.computeNTProofStr( 

2115 ResponseKeyNT, 

2116 Context.chall_tok.ServerChallenge, 

2117 ) 

2118 if NTProofStr == auth_tok.NtChallengeResponse.NTProofStr: 

2119 return True 

2120 return False 

2121 

2122 

2123class NTLMSSP_DOMAIN(NTLMSSP): 

2124 """ 

2125 A variant of the NTLMSSP to be used in server mode that gets the session 

2126 keys from the domain using a Netlogon channel. 

2127 

2128 This has the same arguments as NTLMSSP, but supports the following in server 

2129 mode: 

2130 

2131 :param UPN: the UPN of the machine account to login for Netlogon. 

2132 :param HASHNT: the HASHNT of the machine account (use Netlogon secure channel). 

2133 :param ssp: a KerberosSSP to use (use Kerberos secure channel). 

2134 :param PASSWORD: the PASSWORD of the machine account to use for Netlogon. 

2135 :param DC_IP: (optional) specify the IP of the DC. 

2136 

2137 Netlogon example:: 

2138 

2139 >>> mySSP = NTLMSSP_DOMAIN( 

2140 ... UPN="Server1@domain.local", 

2141 ... HASHNT=bytes.fromhex("8846f7eaee8fb117ad06bdd830b7586c"), 

2142 ... ) 

2143 

2144 Kerberos example:: 

2145 

2146 >>> mySSP = NTLMSSP_DOMAIN( 

2147 ... UPN="Server1@domain.local", 

2148 ... KEY=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, 

2149 ... key=bytes.fromhex( 

2150 ... "85abb9b61dc2fa49d4cc04317bbd108f8f79df28" 

2151 ... "239155ed7b144c5d2ebcf016" 

2152 ... ) 

2153 ... ), 

2154 ... ) 

2155 """ 

2156 

2157 def __init__(self, UPN=None, *args, timeout=3, ssp=None, **kwargs): 

2158 from scapy.layers.kerberos import KerberosSSP 

2159 

2160 # Either PASSWORD or HASHNT or ssp 

2161 if ( 

2162 "HASHNT" not in kwargs 

2163 and "PASSWORD" not in kwargs 

2164 and "KEY" not in kwargs 

2165 and ssp is None 

2166 ): 

2167 raise ValueError( 

2168 "Must specify either 'HASHNT', 'PASSWORD' or " 

2169 "provide a ssp=KerberosSSP()" 

2170 ) 

2171 elif ssp is not None and not isinstance(ssp, KerberosSSP): 

2172 raise ValueError("'ssp' can only be None or a KerberosSSP !") 

2173 

2174 self.KEY = kwargs.pop("KEY", None) 

2175 self.PASSWORD = kwargs.get("PASSWORD", None) 

2176 

2177 # UPN is mandatory 

2178 if UPN is None and ssp is not None and ssp.UPN: 

2179 UPN = ssp.UPN 

2180 elif UPN is None: 

2181 raise ValueError("Must specify a 'UPN' !") 

2182 kwargs["UPN"] = UPN 

2183 

2184 # Call parent 

2185 super(NTLMSSP_DOMAIN, self).__init__( 

2186 *args, 

2187 **kwargs, 

2188 ) 

2189 

2190 # Treat specific parameters 

2191 self.DC_FQDN = kwargs.pop("DC_FQDN", None) 

2192 if self.DC_FQDN is None: 

2193 # Get DC_FQDN from dclocator 

2194 from scapy.layers.ldap import dclocator 

2195 

2196 dc = dclocator( 

2197 self.DOMAIN_FQDN, 

2198 timeout=timeout, 

2199 debug=kwargs.get("debug", 0), 

2200 ) 

2201 self.DC_FQDN = dc.samlogon.DnsHostName.decode().rstrip(".") 

2202 

2203 # If logging in via Kerberos 

2204 self.ssp = ssp 

2205 

2206 def _getSessionBaseKey(self, Context, ntlm): 

2207 """ 

2208 Return the Session Key by asking the DC. 

2209 """ 

2210 # No user / no domain: skip. 

2211 if not ntlm.UserNameLen or not ntlm.DomainNameLen: 

2212 return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm) 

2213 

2214 # Import RPC stuff 

2215 from scapy.layers.dcerpc import NDRUnion 

2216 from scapy.layers.msrpce.msnrpc import ( 

2217 NETLOGON_SECURE_CHANNEL_METHOD, 

2218 NetlogonClient, 

2219 ) 

2220 from scapy.layers.msrpce.raw.ms_nrpc import ( 

2221 NETLOGON_LOGON_IDENTITY_INFO, 

2222 NetrLogonSamLogonWithFlags_Request, 

2223 PNETLOGON_AUTHENTICATOR, 

2224 PNETLOGON_NETWORK_INFO, 

2225 STRING, 

2226 UNICODE_STRING, 

2227 ) 

2228 

2229 # Create NetlogonClient with PRIVACY 

2230 client = NetlogonClient() 

2231 client.connect(self.DC_FQDN) 

2232 

2233 # Establish the Netlogon secure channel (this will bind) 

2234 try: 

2235 if self.ssp is None and self.KEY is None: 

2236 # Login via classic NetlogonSSP 

2237 client.establish_secure_channel( 

2238 mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3, 

2239 UPN=f"{self.COMPUTER_NB_NAME}@{self.DOMAIN_NB_NAME}", 

2240 DC_FQDN=self.DC_FQDN, 

2241 HASHNT=self.HASHNT, 

2242 ) 

2243 else: 

2244 # Login via KerberosSSP (Windows 2025) 

2245 client.establish_secure_channel( 

2246 mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticateKerberos, 

2247 UPN=self.UPN, 

2248 DC_FQDN=self.DC_FQDN, 

2249 PASSWORD=self.PASSWORD, 

2250 KEY=self.KEY, 

2251 ssp=self.ssp, 

2252 ) 

2253 except ValueError: 

2254 log_runtime.warning( 

2255 "Couldn't establish the Netlogon secure channel. " 

2256 "Check the credentials for '%s' !" % self.COMPUTER_NB_NAME 

2257 ) 

2258 return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm) 

2259 

2260 # Request validation of the NTLM request 

2261 req = NetrLogonSamLogonWithFlags_Request( 

2262 LogonServer="", 

2263 ComputerName=self.COMPUTER_NB_NAME, 

2264 Authenticator=client.create_authenticator(), 

2265 ReturnAuthenticator=PNETLOGON_AUTHENTICATOR(), 

2266 LogonLevel=6, # NetlogonNetworkTransitiveInformation 

2267 LogonInformation=NDRUnion( 

2268 tag=6, 

2269 value=PNETLOGON_NETWORK_INFO( 

2270 Identity=NETLOGON_LOGON_IDENTITY_INFO( 

2271 LogonDomainName=UNICODE_STRING( 

2272 Buffer=ntlm.DomainName, 

2273 ), 

2274 ParameterControl=0x00002AE0, 

2275 UserName=UNICODE_STRING( 

2276 Buffer=ntlm.UserName, 

2277 ), 

2278 Workstation=UNICODE_STRING( 

2279 Buffer=ntlm.Workstation, 

2280 ), 

2281 ), 

2282 LmChallenge=Context.chall_tok.ServerChallenge, 

2283 NtChallengeResponse=STRING( 

2284 Buffer=bytes(ntlm.NtChallengeResponse), 

2285 ), 

2286 LmChallengeResponse=STRING( 

2287 Buffer=bytes(ntlm.LmChallengeResponse), 

2288 ), 

2289 ), 

2290 ), 

2291 ValidationLevel=6, 

2292 ExtraFlags=0, 

2293 ndr64=client.ndr64, 

2294 ) 

2295 

2296 # Get response 

2297 resp = client.sr1_req(req) 

2298 if resp and resp.status == 0: 

2299 # Success 

2300 

2301 # Validate DC authenticator 

2302 client.validate_authenticator(resp.ReturnAuthenticator.value) 

2303 

2304 # Get and return the SessionKey 

2305 UserSessionKey = resp.ValidationInformation.value.value.UserSessionKey 

2306 return bytes(UserSessionKey) 

2307 else: 

2308 # Failed 

2309 return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm) 

2310 

2311 def _checkLogin(self, Context, auth_tok): 

2312 # Always OK if we got the session key 

2313 return True