Skip to content

NTLM / SPNEGO / NEGOEX

Back to the Technology Index

NTLM is an authentication method in which the client calculates a response to a server challenge. In Impacket, SMB, LDAP, DCE/RPC, TDS, HTTP, and other protocols share the same NTLM message implementation. For the authentication flow and how it differs from an NT hash, see NTLM Authentication. Message bodies are implemented in ntlm.py, encapsulation in spnego.py, and NEGOEX in negoex.py.

Related: DumpNTLMInfo, ntlmrelayx, rdp_check

USE_NTLMv2 = True # ntlm.py

As the comment indicates, client/server flag exchange does not select NTLMv2. It is used when both local policies use v2. NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY (0x00080000) refers to NTLMv1 Extended Session Security (ESS / NTLM2), not NTLMv2.

computeResponse(..., use_ntlmv2=USE_NTLMv2) is the branch point.

The signature is "NTLMSSP\x00".

ClassTypeContents
NTLMAuthNegotiate1Flags, OEM domain/host, optional VERSION
NTLMAuthChallenge28-byte challenge, TargetInfoFields (AV_PAIRS), optional Version
NTLMAuthChallengeResponse3LM/NT responses, UTF-16 domain/user/host, encrypted session key, Version, MIC area

Entry functions:

  • getNTLMSSPType1(workstation, domain, signingRequired, use_ntlmv2, version)
  • getNTLMSSPType3(type1, type2, user, password, domain, lmhash, nthash, use_ntlmv2, channel_binding_value, service='cifs')

Flags always set by Type1: NTLM | EXTENDED_SESSIONSECURITY | UNICODE | REQUEST_TARGET | 128 | 56. When signing is required: KEY_EXCH | SIGN | ALWAYS_SIGN | SEAL. With v2: TARGET_INFO. The workstation name is retained for Type3, but is not included in Type1 on the wire.

AV_PAIRS is a sequence of <H type><H len><bytes> terminated by NTLMSSP_AV_EOL=0.

IDNameMeaning
0x01HOSTNAMENetBIOS host
0x02DOMAINNAMENetBIOS domain
0x03DNS_HOSTNAMEDNS host
0x04DNS_DOMAINNAMEDNS domain
0x05DNS_TREENAMEForest
0x06FLAGSBits for MIC, restrictions, etc.
0x07TIMEFILETIME
0x08RESTRICTIONSRestrictions
0x09TARGET_NAMESPN written back by the client
0x0aCHANNEL_BINDINGSCBT (MD5 of channel binding struct)

A v2 client may rewrite TargetInfo when constructing Type3 (computeResponseNTLMv2):

  • AV_TARGET_NAME = service/ + DNS_HOSTNAME (default service='cifs')
  • AV_TIME if absent
  • AV_CHANNEL_BINDINGS if channel_binding_value is present

DumpNTLMInfo examines the Type2 AV pairs. MIC, channel binding, and target name, which affect relay success, also appear here.

  • LMOWFv1: DES(KGS!@#$%) on the uppercased OEM password; for non-Latin-1 input, use DEFAULT_LM_HASH = AAD3B435B51404EEAAD3B435B51404EE
  • NTOWFv1: MD4(UTF-16LE(password)) = NT hash
  • Response: encrypt the challenge in three DES blocks
  • With ESS: NT = DES(nthash, MD5(serverChal||clientChal)[:8]); LM = clientChal || 0×16
  • sessionBaseKey = MD4(NT hash)
responseKeyNT = HMAC_MD5(nthash, UPPER(user) || domain)
temp = 0x0101 || 0 || Time || clientChal || 0 || AvPairs
ntProofStr = HMAC_MD5(responseKeyNT, serverChal || temp)
NT response = ntProofStr || temp
LM response = HMAC_MD5(responseKeyNT, serverChal || clientChal) || clientChal
sessionBaseKey= HMAC_MD5(responseKeyNT, ntProofStr)

Anonymous authentication uses empty NT/LM responses and a session key of 16 zero bytes.

keyExchangeKey = KXKEY(...) # Same as sessionBaseKey for v2
if KEY_EXCH:
exportedSessionKey = 16 random bytes
Type3.session_key = RC4(keyExchangeKey, exportedSessionKey)
else:
exportedSessionKey = keyExchangeKey

SMB / RPC / LDAP signing and encryption derive from this exportedSessionKey.

MIC:

MIC = HMAC_MD5(exportedSessionKey, Type1 || Type2 || Type3_with_MIC_zeroed)

getNTLMSSPType3() does not calculate the MIC. Callers that need it, such as LDAP SASL bind, TDS, and ntlmrelayx, fill it in afterward. NTLMAuthChallengeResponse.checkMIC only checks the VERSION flag; it does not actually detect whether a MIC is present.

GSS SIGN/SEAL (SIGNKEY / SEALKEY / MAC / SEAL) is used by DCERPC and LDAP signing. It is calculated separately from SMB message signing.

spnego.py implements BER by hand rather than using pyasn1.

SymbolRole
GSSAPI0x60 + OID 1.3.6.1.5.5.2 + payload
SPNEGO_NegTokenInitmechTypes plus an optional mechToken
SPNEGO_NegTokenRespNegState / SupportedMech / ResponseToken / optional mechListMIC
SPNEGOCipherUses NTLM SIGN/SEAL over SPNEGO (DCERPC / LDAP)

OID:

NameOID
NTLMSSP1.3.6.1.4.1.311.2.2.10
MS KRB51.2.840.48018.1.2.2
KRB51.2.840.113554.1.2.2
KRB5 U2U1.2.840.113554.1.2.2.3
NEGOEX1.3.6.1.4.1.311.2.2.30

An SMB client includes only one mech (NTLM or MS KRB5). The Kerberos token is not a raw AP-REQ; it is

AID + OID(KRB5) + KRB5_AP_REQ + DER(AP_REQ)

The NegState values are 0 completed, 1 incomplete, 2 reject, and 3 request-mic.

SPNEGO_NegTokenResp can emit mechListMIC, but fromString does not parse it.

Equivalent to [MS-NEGOEX]. Signature: NEGOEXTS. Message types: INITIATOR/ACCEPTOR_NEGO, META_DATA, CHALLENGE, AP_REQUEST, VERIFY, and ALERT.

NegoExContext is the state machine. The VERIFY checksum uses RFC 3961, all preceding messages, and key usage 23 (initiator) / 25 (acceptor).

It is not connected to the SMB / LDAP / RPC login paths. It remains an OID definition and standalone module. The PKU2U GUID is also unused.

ntlmrelayx operates at this layer.

ElementEffect
MICDetects Type3 modification; --remove-mic targets CVE-2019-1040
SIGN/SEAL flag--remove-sign-seal clears signing and sealing flags
Channel binding / EPAImpedes relay to LDAPS / HTTPS / TDS
SMB signingImpedes SMB relay
Target name (SPN) AVServer-side target validation
Extended ProtectionFails without channel_binding_value when CBT is required

When adding an inbound server or outbound client, specify four points: ownership of Type1/2/3, MIC recalculation, signing requirements, and CBT.

  • Do not confuse USE_NTLMv2 with the ESS flag
  • Do not assume that the library always adds a MIC to Type3
  • Preserve AV pair order and unknown types
  • Related tests: tests/SMB_RPC/test_ntlm.py