async def negotiate(self): """ """ rply = await self.recvSMB(0) #TODO: check if SMB2 is supported #currently we just continue with SMB2 command = NEGOTIATE_REPLY() command.SecurityMode = NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_ENABLED command.DialectRevision = NegotiateDialects.WILDCARD command.NegotiateContextCount = 0 command.ServerGuid = self.ServerGuid command.Capabilities = 0 command.SystemTime = datetime.datetime.now() command.ServerStartTime = datetime.datetime.now() - datetime.timedelta(days=1) command.Buffer = self.client_gssapi.get_mechtypes_list() header = SMB2Header_SYNC() header.Command = SMB2Command.NEGOTIATE header.CreditReq = 0 msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) print(message_id) rply = await self.recvSMB(1) #recieveing reply, should be version2, because currently we dont support v1 :( #negotiate MessageId should be 1 print('1111111111111111111111111') #TODO: check if SMB2 is supported #currently we just continue with SMB2 command = NEGOTIATE_REPLY() command.SecurityMode = NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_ENABLED command.DialectRevision = NegotiateDialects.SMB202 command.NegotiateContextCount = 0 command.ServerGuid = self.ServerGuid command.Capabilities = 0 command.SystemTime = datetime.datetime.now() command.ServerStartTime = datetime.datetime.now() - datetime.timedelta(days=1) command.Buffer = self.client_gssapi.get_mechtypes_list() header = SMB2Header_SYNC() header.Command = SMB2Command.NEGOTIATE header.CreditReq = 0 msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) self.status = SMBConnectionStatus.SESSIONSETUP return
async def tree_connect(self, share_name): """ share_name MUST be in "\\server\share" format! Server can be NetBIOS name OR IP4 OR IP6 OR FQDN """ if self.session_closed == True: return command = TREE_CONNECT_REQ() command.Path = share_name command.Flags = 0 header = SMB2Header_SYNC() header.Command = SMB2Command.TREE_CONNECT msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) rply = await self.recvSMB(message_id) if rply.header.Status == NTStatus.SUCCESS: te = TreeEntry.from_tree_reply(rply, share_name) self.TreeConnectTable_id[rply.header.TreeId] = te self.TreeConnectTable_share[share_name] = te return te elif rply.header.Status == NTStatus.BAD_NETWORK_NAME: raise SMBIncorrectShareName() elif rply.header.Status == NTStatus.USER_SESSION_DELETED: self.session_closed = True raise SMBException('session delted', NTStatus.USER_SESSION_DELETED) else: raise SMBGenericException()
async def create(self, tree_id, file_path, desired_access, share_mode, create_options, create_disposition, file_attrs, impresonation_level=ImpersonationLevel.Impersonation, oplock_level=OplockLevel.SMB2_OPLOCK_LEVEL_NONE, create_contexts=None, return_reply=False): if self.session_closed == True: return if tree_id not in self.TreeConnectTable_id: raise Exception('Unknown Tree ID!') command = CREATE_REQ() command.RequestedOplockLevel = oplock_level command.ImpersonationLevel = impresonation_level command.DesiredAccess = desired_access command.FileAttributes = file_attrs command.ShareAccess = share_mode command.CreateDisposition = create_disposition command.CreateOptions = create_options command.Name = file_path command.CreateContext = create_contexts header = SMB2Header_SYNC() header.Command = SMB2Command.CREATE header.TreeId = tree_id msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) rply = await self.recvSMB(message_id) if rply.header.Status == NTStatus.SUCCESS: fh = FileHandle.from_create_reply(rply, tree_id, file_path, oplock_level) self.FileHandleTable[fh.file_id] = fh if return_reply == True: return rply.command.FileId, rply.command return rply.command.FileId elif rply.header.Status == NTStatus.ACCESS_DENIED: #this could mean incorrect filename/foldername OR actually access denied raise SMBCreateAccessDenied() else: raise SMBGenericException()
async def read(self, tree_id, file_id, offset=0, length=0): """ Will issue one read command only then waits for reply. To read a whole file you must use a filereader logic! returns the data bytes and the remaining data length IMPORTANT: remaning data length is dependent on the length of the requested chunk (length param) not on the actual file length. to get the remaining length for the actual file you must set the length parameter to the correct file size! If and EOF happens the function returns an empty byte array and the remaining data is set to 0 """ if self.session_closed == True: return if tree_id not in self.TreeConnectTable_id: raise Exception('Unknown Tree ID!') if file_id not in self.FileHandleTable: raise Exception('Unknown File ID!') header = SMB2Header_SYNC() header.Command = SMB2Command.READ header.TreeId = tree_id if length < self.MaxReadSize: length = self.MaxReadSize if self.selected_dialect != NegotiateDialects.SMB202 and self.SupportsMultiCredit == True: header.CreditCharge = (1 + (length - 1) // 65536) else: length = min(65536, length) command = READ_REQ() command.Length = length command.Offset = offset command.FileId = file_id command.MinimumCount = 0 command.RemainingBytes = 0 msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) rply = await self.recvSMB(message_id) if rply.header.Status == NTStatus.SUCCESS: return rply.command.Buffer, rply.command.DataRemaining elif rply.header.Status == NTStatus.END_OF_FILE: return b'', 0 else: raise SMBGenericException()
async def write(self, tree_id, file_id, data, offset=0): """ This function will send one packet only! The data size can be larger than what one packet allows, but it will be truncated to the maximum. Also, there is no guarantee that the actual sent data will be fully written to the remote file! This will be indicated in the returned value. Use a high-level function to get a full write. """ if self.session_closed == True: return if tree_id not in self.TreeConnectTable_id: raise Exception('Unknown Tree ID!') if file_id not in self.FileHandleTable: raise Exception('Unknown File ID!') header = SMB2Header_SYNC() header.Command = SMB2Command.WRITE header.TreeId = tree_id if len(data) > self.MaxWriteSize: data = data[:self.MaxWriteSize] if self.selected_dialect != NegotiateDialects.SMB202 and self.SupportsMultiCredit == True: header.CreditCharge = (1 + (len(data) - 1) // 65536) else: data = data[:min(65536, len(data))] command = WRITE_REQ() command.Length = len(data) command.Offset = offset command.FileId = file_id command.Data = data msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) rply = await self.recvSMB(message_id) if rply.header.Status == NTStatus.SUCCESS: return rply.command.Count else: raise SMBGenericException()
async def session_setup(self): print('session_setup') rply = await self.recvSMB(2) self.SessionId = int.from_bytes(os.urandom(8), 'big', signed=False) auth_data = rply.command.Buffer print('clinet buffer: %s' % auth_data) data, res = await self.client_gssapi.authenticate(auth_data) command = SESSION_SETUP_REPLY() command.SessionFlags = 0 command.Buffer = data header = SMB2Header_SYNC() header.Command = SMB2Command.SESSION_SETUP header.CreditReq = 127 header.Status = NTStatus.MORE_PROCESSING_REQUIRED msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) print(message_id) rply = await self.recvSMB(3)
async def query_info( self, tree_id, file_id, info_type=QueryInfoType.FILE, information_class=FileInfoClass.FileStandardInformation, additional_information=0, flags=0, data_in=''): """ Queires the file or directory for specific information. The information returned is depending on the input parameters, check the documentation on msdn for a better understanding. The resturned data can by raw bytes or an actual object, depending on wther your info is implemented in the library. Sorry there are a TON of classes to deal with :( IMPORTANT: in case you are requesting big amounts of data, the result will arrive in chunks. You will need to invoke this function until None is returned to get the full data!!! """ if self.session_closed == True: return if tree_id not in self.TreeConnectTable_id: raise Exception('Unknown Tree ID!') if file_id not in self.FileHandleTable: raise Exception('Unknown File ID!') command = QUERY_INFO_REQ() command.InfoType = info_type command.FileInfoClass = information_class command.AdditionalInformation = additional_information command.Flags = flags command.FileId = file_id command.Data = data_in header = SMB2Header_SYNC() header.Command = SMB2Command.QUERY_INFO header.TreeId = tree_id msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) rply = await self.recvSMB(message_id) if rply.header.Status == NTStatus.SUCCESS: #https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/3b1b3598-a898-44ca-bfac-2dcae065247f if info_type == QueryInfoType.SECURITY: return SECURITY_DESCRIPTOR.from_bytes(rply.command.Data) elif info_type == QueryInfoType.FILE: if information_class == FileInfoClass.FileFullDirectoryInformation: return FileFullDirectoryInformationList.from_bytes( rply.command.Data) else: return rply.command.Data elif info_type == QueryInfoType.FILESYSTEM: #TODO: implement this return rply.command.Data elif info_type == QueryInfoType.QUOTA: #TODO: implement this return rply.command.Data else: #this should never happen return rply.command.Data else: raise SMBGenericException()
async def session_setup(self, fake_auth=False): authdata = None status = NTStatus.MORE_PROCESSING_REQUIRED maxiter = 5 while status == NTStatus.MORE_PROCESSING_REQUIRED and maxiter > 0: command = SESSION_SETUP_REQ() try: command.Buffer, res = await self.gssapi.authenticate(authdata) if fake_auth == True: if self.gssapi.selected_authentication_context is not None and self.gssapi.selected_authentication_context.ntlmChallenge is not None: return except Exception as e: logger.exception('GSSAPI auth failed!') #TODO: Clear this up, kerberos lib needs it's own exceptions! if str(e).find('Preauth') != -1: raise SMBKerberosPreauthFailed() else: raise e #raise SMBKerberosPreauthFailed() command.Flags = 0 command.SecurityMode = NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_ENABLED command.Capabilities = 0 command.Channel = 0 command.PreviousSessionId = 0 header = SMB2Header_SYNC() header.Command = SMB2Command.SESSION_SETUP header.CreditReq = 127 msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) rply = await self.recvSMB(message_id) if self.SessionId == 0: self.SessionId = rply.header.SessionId if rply.header.Status not in [ NTStatus.SUCCESS, NTStatus.MORE_PROCESSING_REQUIRED ]: break authdata = rply.command.Buffer status = rply.header.Status maxiter -= 1 if rply.header.Status == NTStatus.SUCCESS: self.SessionKey = self.gssapi.get_session_key()[:16] # TODO: key calc if self.signing_required and self.selected_dialect in [ NegotiateDialects.SMB300, NegotiateDialects.SMB302, NegotiateDialects.SMB311 ]: self.SigningKey = crypto.KDF_CounterMode( self.SessionKey, b"SMB2AESCMAC\x00", "SmbSign\x00", 128) self.ApplicationKey = crypto.KDF_CounterMode( self.SessionKey, b"SMB2APP\x00", "SmbRpc\x00", 128) self.EncryptionKey = crypto.KDF_CounterMode( self.SessionKey, b"SMB2AESCCM\x00", "ServerIn \x00", 128) self.DecryptionKey = crypto.KDF_CounterMode( self.SessionKey, b"SMB2AESCCM\x00", "ServerOut\x00", 128) self.status = SMBConnectionStatus.RUNNING elif rply.header.Status == NTStatus.LOGON_FAILURE: raise SMBAuthenticationFailed() else: raise SMBException( 'session_setup (authentication probably failed)', rply.header.Status)
async def negotiate(self): """ Initiates protocol negotiation. First we send an SMB_COM_NEGOTIATE_REQ with our supported dialects """ #let's construct an SMBv1 SMB_COM_NEGOTIATE_REQ packet header = SMBHeader() header.Command = SMBCommand.SMB_COM_NEGOTIATE header.Status = NTStatus.SUCCESS header.Flags = 0 header.Flags2 = SMBHeaderFlags2Enum.SMB_FLAGS2_UNICODE command = SMB_COM_NEGOTIATE_REQ() command.Dialects = ['SMB 2.???'] msg = SMBMessage(header, command) message_id = await self.sendSMB(msg) #recieveing reply, should be version2, because currently we dont support v1 :( rply = await self.recvSMB(message_id) #negotiate MessageId should be 1 if rply.header.Status == NTStatus.SUCCESS: if isinstance(rply, SMB2Message): if rply.command.DialectRevision == NegotiateDialects.WILDCARD: command = NEGOTIATE_REQ() command.SecurityMode = NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_ENABLED | NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_REQUIRED command.Capabilities = 0 command.ClientGuid = self.ClientGUID command.Dialects = self.dialects header = SMB2Header_SYNC() header.Command = SMB2Command.NEGOTIATE header.CreditReq = 0 msg = SMB2Message(header, command) message_id = await self.sendSMB(msg) rply = await self.recvSMB( message_id) #negotiate MessageId should be 1 if rply.header.Status != NTStatus.SUCCESS: print('session got reply!') print(rply) raise Exception( 'session_setup_1 (authentication probably failed) reply: %s' % rply.header.Status) if rply.command.DialectRevision not in self.supported_dialects: raise SMBUnsupportedDialectSelected() self.selected_dialect = rply.command.DialectRevision self.signing_required = NegotiateSecurityMode.SMB2_NEGOTIATE_SIGNING_ENABLED in rply.command.SecurityMode logger.log( 1, 'Server selected dialect: %s' % self.selected_dialect) self.MaxTransactSize = min(0x100000, rply.command.MaxTransactSize) self.MaxReadSize = min(0x100000, rply.command.MaxReadSize) self.MaxWriteSize = min(0x100000, rply.command.MaxWriteSize) self.ServerGuid = rply.command.ServerGuid self.SupportsMultiChannel = NegotiateCapabilities.MULTI_CHANNEL in rply.command.Capabilities else: logger.error( 'Server choose SMB v1 which is not supported currently') raise SMBUnsupportedSMBVersion() else: print('session got reply!') print(rply) raise Exception( 'session_setup_1 (authentication probably failed) reply: %s' % rply.header.Status) self.status = SMBConnectionStatus.SESSIONSETUP