5151from pyasn1 .codec .der import encoder , decoder
5252
5353# For signing
54- from impacket import smb , nmb , ntlm , uuid
54+ from Cryptodome .Hash import SHA512
55+ from impacket import smb , nmb , ntlm , uuid , crypto
5556from impacket import smb3structs as smb2
5657from impacket .spnego import SPNEGO_NegTokenInit , TypesMech , MechTypes , SPNEGO_NegTokenResp , ASN1_AID , \
5758 ASN1_SUPPORTED_MECH
7273STATUS_SMB_BAD_UID = 0x005B0002
7374STATUS_SMB_BAD_TID = 0x00050002
7475
76+ _SUPPORTED_DIALECTS = (smb2 .SMB2_DIALECT_311 , smb2 .SMB2_DIALECT_21 , smb2 .SMB2_DIALECT_002 )
77+ _PREAUTH_HASH_ZERO = b'\x00 ' * 64
78+
79+
80+ def _preauth_hash_update (current : bytes , data : bytes ) -> bytes :
81+ """SHA-512(current || data), one step of the SMB 3.1.1 pre-auth integrity hash chain.
82+
83+ MS-SMB2 §3.3.5.4 (negotiate) and §3.3.5.5.3 (session setup) describe how the server
84+ iteratively hashes request and response bytes into the connection/session pre-auth value:
85+ https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/b39f253e-4963-40df-8dff-2f9040ebbeb1
86+ """
87+ h = SHA512 .new ()
88+ h .update (current )
89+ h .update (data )
90+ return h .digest ()
91+
92+
93+ def _build_smb311_preauth_context () -> bytes :
94+ """Build a serialised SMB2_PREAUTH_INTEGRITY_CAPABILITIES negotiate context (SHA-512, no salt).
95+
96+ MS-SMB2 §2.2.3.1.1, SMB2_PREAUTH_INTEGRITY_CAPABILITIES structure definition:
97+ https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5a07bd66-4734-4af8-abcf-5a44ff7ee0e5
98+ """
99+ preauth = smb2 .SMB2PreAuthIntegrityCapabilities ()
100+ preauth ['HashAlgorithmCount' ] = 1
101+ preauth ['SaltLength' ] = 0
102+ preauth ['HashAlgorithms' ] = struct .pack ('<H' , 0x0001 )
103+ preauth ['Salt' ] = b''
104+ ctx = smb2 .SMB2NegotiateContext ()
105+ ctx ['ContextType' ] = smb2 .SMB2_PREAUTH_INTEGRITY_CAPABILITIES
106+ ctx_data = preauth .getData ()
107+ ctx ['DataLength' ] = len (ctx_data )
108+ ctx ['Reserved' ] = 0
109+ ctx ['Data' ] = ctx_data
110+ return ctx .getData ()
111+
75112
76113# Utility functions
77114# and general functions.
@@ -2834,7 +2871,7 @@ def default(connId, smbServer, SMBCommand, recvPacket):
28342871
28352872class SMB2Commands :
28362873 @staticmethod
2837- def smb2Negotiate (connId , smbServer , recvPacket , isSMB1 = False ):
2874+ def smbNegotiate (connId , smbServer , recvPacket , isSMB1 = False ):
28382875 connData = smbServer .getConnectionData (connId , checkStatus = False )
28392876
28402877 respPacket = smb2 .SMB2Packet ()
@@ -2857,13 +2894,21 @@ def smb2Negotiate(connId, smbServer, recvPacket, isSMB1=False):
28572894 SMBCommand = smb .SMBCommand (recvPacket ['Data' ][0 ])
28582895
28592896 dialects = SMBCommand ['Data' ].split (b'\x02 ' )
2860- if b'SMB 2.002\x00 ' in dialects or b'SMB 2.???\x00 ' in dialects :
2897+ if b'SMB 2.???\x00 ' in dialects :
2898+ respSMBCommand ['DialectRevision' ] = smb2 .SMB2_DIALECT_WILDCARD
2899+ connData ['Dialect' ] = smb2 .SMB2_DIALECT_WILDCARD
2900+ elif b'SMB 2.002\x00 ' in dialects :
28612901 respSMBCommand ['DialectRevision' ] = smb2 .SMB2_DIALECT_002
2902+ connData ['Dialect' ] = smb2 .SMB2_DIALECT_002
28622903 else :
2863- # Client does not support SMB2 fallbacking
28642904 raise Exception ('SMB2 not supported, fallbacking' )
28652905 else :
2866- respSMBCommand ['DialectRevision' ] = smb2 .SMB2_DIALECT_002
2906+ negRequest = smb2 .SMB2Negotiate (recvPacket ['Data' ])
2907+ offered = set (negRequest ['Dialects' ])
2908+ selected = next ((d for d in _SUPPORTED_DIALECTS if d in offered ), smb2 .SMB2_DIALECT_002 )
2909+ respSMBCommand ['DialectRevision' ] = selected
2910+ connData ['Dialect' ] = selected
2911+
28672912 respSMBCommand ['ServerGuid' ] = b'A' * 16
28682913 respSMBCommand ['Capabilities' ] = 0
28692914 respSMBCommand ['MaxTransactSize' ] = 65536
@@ -2886,12 +2931,40 @@ def smb2Negotiate(connId, smbServer, recvPacket, isSMB1=False):
28862931 respSMBCommand ['Buffer' ] = blob .getData ()
28872932 respSMBCommand ['SecurityBufferLength' ] = len (respSMBCommand ['Buffer' ])
28882933
2889- respPacket ['Data' ] = respSMBCommand
2934+ if connData .get ('Dialect' ) == smb2 .SMB2_DIALECT_311 :
2935+ SMB2Commands ._smb311_negotiate (connData , recvPacket , respSMBCommand , respPacket )
2936+ else :
2937+ respPacket ['Data' ] = respSMBCommand
28902938
28912939 smbServer .setConnectionData (connId , connData )
28922940
28932941 return None , [respPacket ], STATUS_SUCCESS
28942942
2943+ @staticmethod
2944+ def _smb311_negotiate (connData : dict , recvPacket , respSMBCommand , respPacket ) -> None :
2945+ """Handle SMB 3.1.1 negotiate: initialise pre-auth hash chain and append mandatory negotiate context.
2946+
2947+ MS-SMB2 §3.3.5.4, Receiving an SMB2 NEGOTIATE Request (3.1.1 NegotiateContextList processing is inline):
2948+ https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/b39f253e-4963-40df-8dff-2f9040ebbeb1
2949+ """
2950+ connData ['PreauthIntegrityHashValue' ] = _preauth_hash_update (
2951+ _PREAUTH_HASH_ZERO , recvPacket .rawData )
2952+
2953+ # Encryption context omitted intentionally: advertising it would cause the client to enable
2954+ # session encryption, which this server does not implement.
2955+ ctx_bytes = _build_smb311_preauth_context ()
2956+ sec_buf_len = len (respSMBCommand ['Buffer' ])
2957+ ctx_pad = (8 - sec_buf_len % 8 ) % 8
2958+ respSMBCommand ['NegotiateContextCount' ] = 1
2959+ respSMBCommand ['NegotiateContextOffset' ] = 0x80 + sec_buf_len + ctx_pad
2960+ respSMBCommand ['Padding' ] = b'\x00 ' * ctx_pad
2961+ respSMBCommand ['NegotiateContextList' ] = ctx_bytes
2962+
2963+ respPacket ['Data' ] = respSMBCommand
2964+
2965+ connData ['PreauthIntegrityHashValue' ] = _preauth_hash_update (
2966+ connData ['PreauthIntegrityHashValue' ], respPacket .getData ())
2967+
28952968 @staticmethod
28962969 def _kerberos_auth (token , connData , smbServer ):
28972970 try :
@@ -3247,12 +3320,54 @@ def smb2SessionSetup(connId, smbServer, recvPacket):
32473320 else :
32483321 smbServer .log ("Unknown or unsupported security blob type" , logging .ERROR , connData = connData )
32493322 return [SMB2Commands .generic_negTokenResp ()], None , STATUS_MORE_PROCESSING_REQUIRED
3250-
3323+
3324+ is_311 = (connData .get ('Dialect' ) == smb2 .SMB2_DIALECT_311 )
3325+
3326+ if is_311 :
3327+ # MS-SMB2 §3.3.5.5.3, Handling GSS-API Authentication:
3328+ # the session pre-auth hash is forked from the connection hash at the first
3329+ # SESSION_SETUP round and updated with each request/response pair.
3330+ # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5ed93f06-a1d2-4837-8954-fa8b833c2654
3331+ is_ntlm_round1 = (token [:8 ] == b'NTLMSSP\x00 ' and len (token ) >= 12
3332+ and struct .unpack ('<L' , token [8 :12 ])[0 ] == 0x01 )
3333+ if is_ntlm_round1 or 'SessionPreauthIntegrityHashValue' not in connData :
3334+ connData ['SessionPreauthIntegrityHashValue' ] = connData .get ('PreauthIntegrityHashValue' , _PREAUTH_HASH_ZERO )
3335+ connData ['SessionPreauthIntegrityHashValue' ] = _preauth_hash_update (
3336+ connData ['SessionPreauthIntegrityHashValue' ], recvPacket .rawData )
3337+
32513338 if authType in [TypesMech ['MS KRB5 - Microsoft Kerberos 5' ], TypesMech ['KRB5 - Kerberos 5' ], TypesMech ['KRB5 - Kerberos 5 - User to User' ]]:
32523339 respSMBCommand , errorCode = SMB2Commands ._kerberos_auth (token , connData , smbServer )
32533340 elif authType == TypesMech ['NTLMSSP - Microsoft NTLM Security Support Provider' ]:
32543341 respSMBCommand , errorCode = SMB2Commands ._ntlm_auth (token , connData , smbServer , rawNTLM )
32553342
3343+ if is_311 :
3344+ if errorCode == STATUS_MORE_PROCESSING_REQUIRED :
3345+ resp_pkt = smb2 .SMB2Packet ()
3346+ resp_pkt ['Flags' ] = smb2 .SMB2_FLAGS_SERVER_TO_REDIR
3347+ resp_pkt ['Status' ] = errorCode
3348+ resp_pkt ['CreditRequestResponse' ] = recvPacket ['CreditRequestResponse' ]
3349+ resp_pkt ['Command' ] = recvPacket ['Command' ]
3350+ resp_pkt ['CreditCharge' ] = recvPacket ['CreditCharge' ]
3351+ resp_pkt ['Reserved' ] = recvPacket ['Reserved' ]
3352+ resp_pkt ['SessionID' ] = connData ['Uid' ]
3353+ resp_pkt ['MessageID' ] = recvPacket ['MessageID' ]
3354+ resp_pkt ['TreeID' ] = recvPacket ['TreeID' ]
3355+ resp_pkt ['Data' ] = respSMBCommand .getData ()
3356+ connData ['SessionPreauthIntegrityHashValue' ] = _preauth_hash_update (
3357+ connData ['SessionPreauthIntegrityHashValue' ], resp_pkt .getData ())
3358+ elif errorCode == STATUS_SUCCESS :
3359+ # Derive the SMB 3.1.1 signing key via SP 800-108 counter-mode KBKDF.
3360+ # Label=b"SMBSigningKey\x00", Context=SessionPreauthIntegrityHashValue, L=128 bits.
3361+ # MS-SMB2 §3.1.4.2, Generating Cryptographic Keys:
3362+ # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/da4e579e-02ce-4e27-bbce-3fc816a3ff92
3363+ # NIST SP 800-108r1, KDF in Counter Mode (referenced from §3.1.4.2 as [SP800-108]):
3364+ # https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1.pdf
3365+ session_key = connData .get ('SigningSessionKey' , b'' )
3366+ if session_key :
3367+ connData ['SigningSessionKey' ] = crypto .KDF_CounterMode (
3368+ session_key , b'SMBSigningKey\x00 ' ,
3369+ connData ['SessionPreauthIntegrityHashValue' ], 128 )
3370+
32563371 # From now on, the client can ask for other commands
32573372 connData ['Authenticated' ] = True
32583373 # For now, just switching to nobody
@@ -3324,7 +3439,10 @@ def smb2TreeConnect(connId, smbServer, recvPacket):
33243439
33253440 # Sign the packet if needed
33263441 if connData ['SignatureEnabled' ]:
3327- smbServer .signSMBv2 (respPacket , connData ['SigningSessionKey' ])
3442+ if connData .get ('Dialect' , smb2 .SMB2_DIALECT_002 ) >= smb2 .SMB2_DIALECT_30 :
3443+ smbServer .signSMBv3 (respPacket , connData ['SigningSessionKey' ])
3444+ else :
3445+ smbServer .signSMBv2 (respPacket , connData ['SigningSessionKey' ])
33283446 smbServer .setConnectionData (connId , connData )
33293447
33303448 return None , [respPacket ], errorCode
@@ -4143,7 +4261,7 @@ def fsctlValidateNegotiateInfo(connId, smbServer, ioctlRequest):
41434261 validateNegotiateInfoResponse ['Capabilities' ] = 0
41444262 validateNegotiateInfoResponse ['Guid' ] = b'A' * 16
41454263 validateNegotiateInfoResponse ['SecurityMode' ] = 1
4146- validateNegotiateInfoResponse ['Dialect' ] = smb2 .SMB2_DIALECT_002
4264+ validateNegotiateInfoResponse ['Dialect' ] = connData . get ( 'Dialect' , smb2 .SMB2_DIALECT_002 )
41474265
41484266 smbServer .setConnectionData (connId , connData )
41494267 return validateNegotiateInfoResponse .getData (), errorCode
@@ -4350,7 +4468,7 @@ def __init__(self, server_address, handler_class=SMBSERVERHandler, config_parser
43504468 }
43514469
43524470 self .__smb2Commands = {
4353- smb2 .SMB2_NEGOTIATE : self .__smb2CommandsHandler .smb2Negotiate ,
4471+ smb2 .SMB2_NEGOTIATE : self .__smb2CommandsHandler .smbNegotiate ,
43544472 smb2 .SMB2_SESSION_SETUP : self .__smb2CommandsHandler .smb2SessionSetup ,
43554473 smb2 .SMB2_LOGOFF : self .__smb2CommandsHandler .smb2Logoff ,
43564474 smb2 .SMB2_TREE_CONNECT : self .__smb2CommandsHandler .smb2TreeConnect ,
@@ -4659,7 +4777,18 @@ def signSMBv2(self, packet, signingSessionKey, padLength=0):
46594777 packetData = packet .getData () + b'\x00 ' * padLength
46604778 signature = hmac .new (signingSessionKey , packetData , hashlib .sha256 ).digest ()
46614779 packet ['Signature' ] = signature [:16 ]
4662- # print "%s" % packet['Signature'].encode('hex')
4780+
4781+ def signSMBv3 (self , packet , signingKey , padLength = 0 ):
4782+ """Sign an SMB 3.x packet with AES-CMAC using the derived signing key.
4783+
4784+ MS-SMB2 §3.1.4.1, Signing an Outgoing Message (SMB 3.x uses AES-CMAC per RFC 4493):
4785+ https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/a3e9ea1e-53c8-4cff-94bd-d98fb20417c0
4786+ """
4787+ packet ['Signature' ] = b'\x00 ' * 16
4788+ packet ['Flags' ] |= smb2 .SMB2_FLAGS_SIGNED
4789+ packetData = packet .getData () + b'\x00 ' * padLength
4790+ signature = crypto .AES_CMAC (signingKey , packetData , len (packetData ))
4791+ packet ['Signature' ] = signature [:16 ]
46634792
46644793 def processRequest (self , connId , data ):
46654794
@@ -4890,7 +5019,10 @@ def processRequest(self, connId, data):
48905019 packet ['NextCommand' ] = len (packet ) + padLen
48915020
48925021 if connData ['SignatureEnabled' ]:
4893- self .signSMBv2 (packet , connData ['SigningSessionKey' ], padLength = padLen )
5022+ if connData .get ('Dialect' , smb2 .SMB2_DIALECT_002 ) >= smb2 .SMB2_DIALECT_30 :
5023+ self .signSMBv3 (packet , connData ['SigningSessionKey' ], padLength = padLen )
5024+ else :
5025+ self .signSMBv2 (packet , connData ['SigningSessionKey' ], padLength = padLen )
48945026
48955027 if hasattr (packet , 'getData' ):
48965028 finalData .append (packet .getData () + padLen * b'\x00 ' )
0 commit comments