@@ -589,6 +589,15 @@ def ntlmssp_DES_encrypt(key, challenge):
589589
590590# High level functions to use NTLMSSP
591591
592+ def _default_ntlm_version ():
593+ """Return a VERSION structure that looks like a current Windows 10/11 client."""
594+ v = VERSION ()
595+ v ['ProductMajorVersion' ] = 10
596+ v ['ProductMinorVersion' ] = 0
597+ v ['ProductBuild' ] = random .choice ([19041 , 19042 , 19044 , 19045 , 22000 , 22621 , 26100 ])
598+ v ['NTLMRevisionCurrent' ] = VERSION .NTLMSSP_REVISION_W2K3
599+ return v .getData ()
600+
592601def getNTLMSSPType1 (workstation = '' , domain = '' , signingRequired = False , use_ntlmv2 = USE_NTLMv2 , version = None ):
593602 # Let's do some encoding checks before moving on. Kind of dirty, but found effective when dealing with
594603 # international characters.
@@ -608,16 +617,16 @@ def getNTLMSSPType1(workstation='', domain='', signingRequired = False, use_ntlm
608617 auth = NTLMAuthNegotiate ()
609618 auth ['flags' ]= 0
610619 if signingRequired :
611- auth ['flags' ] = NTLMSSP_NEGOTIATE_KEY_EXCH | NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_ALWAYS_SIGN | \
612- NTLMSSP_NEGOTIATE_SEAL
620+ auth ['flags' ] = NTLMSSP_NEGOTIATE_KEY_EXCH | NTLMSSP_NEGOTIATE_SIGN | NTLMSSP_NEGOTIATE_SEAL
613621 if use_ntlmv2 :
614622 auth ['flags' ] |= NTLMSSP_NEGOTIATE_TARGET_INFO
615623 auth ['flags' ] |= NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY | NTLMSSP_NEGOTIATE_UNICODE | \
616- NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_128 | NTLMSSP_NEGOTIATE_56
624+ NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_128 | NTLMSSP_NEGOTIATE_56 | \
625+ NTLMSSP_NEGOTIATE_ALWAYS_SIGN | NTLMSSP_NEGOTIATE_VERSION
617626
618- if version is not None :
619- auth [ 'flags' ] |= NTLMSSP_NEGOTIATE_VERSION
620- auth ['os_version' ] = version
627+ if version is None :
628+ version = _default_ntlm_version ()
629+ auth ['os_version' ] = version
621630
622631 # We're not adding workstation / domain fields this time. Normally Windows clients don't add such information but,
623632 # we will save the workstation name to be used later.
@@ -702,7 +711,7 @@ def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash =
702711 if ntlmChallenge ['flags' ] & NTLMSSP_NEGOTIATE_KEY_EXCH :
703712 # not exactly what I call random tho :\
704713 # exportedSessionKey = this is the key we should use to sign
705- exportedSessionKey = b ( "" . join ([ random . choice ( string . digits + string . ascii_letters ) for _ in range ( 16 )]) )
714+ exportedSessionKey = os . urandom ( 16 )
706715 #exportedSessionKey = "A"*16
707716 #print "keyExchangeKey %r" % keyExchangeKey
708717 # Let's generate the right session key based on the challenge flags
@@ -728,20 +737,47 @@ def getNTLMSSPType3(type1, type2, user, password, domain, lmhash = '', nthash =
728737 # [MS-NLMP] page 46
729738 exportedSessionKey = keyExchangeKey
730739
731- ntlmChallengeResponse ['flags' ] = responseFlags
732- ntlmChallengeResponse ['domain_name' ] = domain .encode ('utf-16le' )
740+ # If the server's CHALLENGE_MESSAGE included a Timestamp AV pair we are
741+ # expected to compute a MIC and zero out the LM response per [MS-NLMP]
742+ # 3.1.5.1.2. This is what every modern Windows client does.
743+ challenge_av = AV_PAIRS (serverName ) if serverName else None
744+ have_timestamp = challenge_av is not None and challenge_av [NTLMSSP_AV_TIME ] is not None
745+ compute_mic = have_timestamp and use_ntlmv2
746+
747+ # Use the NetBIOS domain advertised by the server when available, instead
748+ # of the caller-supplied value.
749+ if challenge_av is not None and challenge_av [NTLMSSP_AV_DOMAINNAME ] is not None :
750+ wire_domain = challenge_av [NTLMSSP_AV_DOMAINNAME ][1 ]
751+ else :
752+ wire_domain = domain .encode ('utf-16le' )
753+
754+ ntlmChallengeResponse ['flags' ] = responseFlags | NTLMSSP_NEGOTIATE_VERSION
755+ ntlmChallengeResponse ['domain_name' ] = wire_domain
733756 ntlmChallengeResponse ['host_name' ] = type1 .getWorkstation ().encode ('utf-16le' )
734- if lmResponse == '' :
757+ if compute_mic :
758+ # When timestamp is present the spec says LM response MUST be Z(24).
759+ ntlmChallengeResponse ['lanman' ] = b'\x00 ' * 24
760+ elif lmResponse == '' :
735761 ntlmChallengeResponse ['lanman' ] = b'\x00 '
736762 else :
737763 ntlmChallengeResponse ['lanman' ] = lmResponse
738764
739- if version is not None :
740- ntlmChallengeResponse ['Version' ] = version
765+ if version is None :
766+ version = _default_ntlm_version ()
767+ ntlmChallengeResponse ['Version' ] = version
741768 ntlmChallengeResponse ['ntlm' ] = ntResponse
742- if encryptedRandomSessionKey is not None :
769+ if encryptedRandomSessionKey is not None :
743770 ntlmChallengeResponse ['session_key' ] = encryptedRandomSessionKey
744771
772+ if compute_mic :
773+ # Reserve space for the MIC, serialize, then patch in the real value.
774+ ntlmChallengeResponse ['MIC' ] = b'\x00 ' * 16
775+ type1_data = type1 .getData () if hasattr (type1 , 'getData' ) else bytes (type1 )
776+ type2_data = type2 if isinstance (type2 , (bytes , bytearray )) else bytes (type2 )
777+ type3_zero = ntlmChallengeResponse .getData ()
778+ mic = hmac_md5 (exportedSessionKey , type1_data + type2_data + type3_zero )
779+ ntlmChallengeResponse ['MIC' ] = mic
780+
745781 return ntlmChallengeResponse , exportedSessionKey
746782
747783
@@ -956,17 +992,27 @@ def computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, d
956992 # level
957993 if TEST_CASE is False :
958994 av_pairs [NTLMSSP_AV_TARGET_NAME ] = f"{ service } /" .encode ('utf-16le' ) + av_pairs [NTLMSSP_AV_DNS_HOSTNAME ][1 ]
959- if av_pairs [NTLMSSP_AV_TIME ] is not None :
995+ timestamp_present = av_pairs [NTLMSSP_AV_TIME ] is not None
996+ if timestamp_present :
960997 aTime = av_pairs [NTLMSSP_AV_TIME ][1 ]
961998 else :
962999 aTime = struct .pack ('<q' , (116444736000000000 + calendar .timegm (time .gmtime ()) * 10000000 ) )
9631000 av_pairs [NTLMSSP_AV_TIME ] = aTime
1001+ # Always provide an MsvAvFlags entry. Bit 0x2 indicates the client
1002+ # provides a MIC, which is the case whenever the server sent a
1003+ # Timestamp AV pair in the challenge.
1004+ flags_value = 0x00000002 if timestamp_present else 0x00000000
1005+ av_pairs [NTLMSSP_AV_FLAGS ] = struct .pack ('<L' , flags_value )
9641006 serverName = av_pairs .getData ()
9651007 else :
9661008 aTime = b'\x00 ' * 8
967-
1009+
9681010 if len (channel_binding_value ) > 0 :
9691011 av_pairs [NTLMSSP_AV_CHANNEL_BINDINGS ] = channel_binding_value
1012+ elif TEST_CASE is False and av_pairs [NTLMSSP_AV_TIME ] is not None :
1013+ # Spec: when MsvAvTimestamp is present and there is no real channel
1014+ # binding, set MsvAvChannelBindings to Z(16).
1015+ av_pairs [NTLMSSP_AV_CHANNEL_BINDINGS ] = b'\x00 ' * 16
9701016
9711017 # Format according to:
9721018 # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/aee311d6-21a7-4470-92a5-c4ecb022a87b
0 commit comments