Coverage for /root/GitHubProjects/impacket/impacket/smbconnection.py : 60%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Impacket - Collection of Python classes for working with network protocols.
2#
3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved.
4#
5# This software is provided under a slightly modified version
6# of the Apache Software License. See the accompanying LICENSE file
7# for more information.
8#
9# Description:
10# Wrapper class for SMB1/2/3 so it's transparent for the client.
11# You can still play with the low level methods (version dependent)
12# by calling getSMBServer()
13#
14# Author: Alberto Solino (@agsolino)
15#
17import ntpath
18import socket
20from impacket import smb, smb3, nmb, nt_errors, LOG
21from impacket.ntlm import compute_lmhash, compute_nthash
22from impacket.smb3structs import SMB2Packet, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, GENERIC_ALL, FILE_SHARE_READ, \
23 FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITE_IF, FILE_ATTRIBUTE_NORMAL, \
24 SMB2_IL_IMPERSONATION, SMB2_OPLOCK_LEVEL_NONE, FILE_READ_DATA , FILE_WRITE_DATA, FILE_OPEN, GENERIC_READ, GENERIC_WRITE, \
25 FILE_OPEN_REPARSE_POINT, MOUNT_POINT_REPARSE_DATA_STRUCTURE, FSCTL_SET_REPARSE_POINT, SMB2_0_IOCTL_IS_FSCTL, \
26 MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE, FSCTL_DELETE_REPARSE_POINT, FSCTL_SRV_ENUMERATE_SNAPSHOTS, SRV_SNAPSHOT_ARRAY, \
27 FILE_SYNCHRONOUS_IO_NONALERT, FILE_READ_EA, FILE_READ_ATTRIBUTES, READ_CONTROL, SYNCHRONIZE, SMB2_DIALECT_311
30# So the user doesn't need to import smb, the smb3 are already in here
31SMB_DIALECT = smb.SMB_DIALECT
33class SMBConnection:
34 """
35 SMBConnection class
37 :param string remoteName: name of the remote host, can be its NETBIOS name, IP or *\\*SMBSERVER*. If the later,
38 and port is 139, the library will try to get the target's server name.
39 :param string remoteHost: target server's remote address (IPv4, IPv6) or FQDN
40 :param string/optional myName: client's NETBIOS name
41 :param integer/optional sess_port: target port to connect
42 :param integer/optional timeout: timeout in seconds when receiving packets
43 :param optional preferredDialect: the dialect desired to talk with the target server. If not specified the highest
44 one available will be used
45 :param optional boolean manualNegotiate: the user manually performs SMB_COM_NEGOTIATE
47 :return: a SMBConnection instance, if not raises a SessionError exception
48 """
50 def __init__(self, remoteName='', remoteHost='', myName=None, sess_port=nmb.SMB_SESSION_PORT, timeout=60, preferredDialect=None,
51 existingConnection=None, manualNegotiate=False):
53 self._SMBConnection = 0
54 self._dialect = ''
55 self._nmbSession = 0
56 self._sess_port = sess_port
57 self._myName = myName
58 self._remoteHost = remoteHost
59 self._remoteName = remoteName
60 self._timeout = timeout
61 self._preferredDialect = preferredDialect
62 self._existingConnection = existingConnection
63 self._manualNegotiate = manualNegotiate
64 self._doKerberos = False
65 self._kdcHost = None
66 self._useCache = True
67 self._ntlmFallback = True
69 if existingConnection is not None: 69 ↛ 71line 69 didn't jump to line 71, because the condition on line 69 was never true
70 # Existing Connection must be a smb or smb3 instance
71 assert ( isinstance(existingConnection,smb.SMB) or isinstance(existingConnection, smb3.SMB3))
72 self._SMBConnection = existingConnection
73 self._preferredDialect = self._SMBConnection.getDialect()
74 self._doKerberos = self._SMBConnection.getKerberos()
75 return
77 ##preferredDialect = smb.SMB_DIALECT
79 if manualNegotiate is False:
80 self.negotiateSession(preferredDialect)
82 def negotiateSession(self, preferredDialect=None,
83 flags1=smb.SMB.FLAGS1_PATHCASELESS | smb.SMB.FLAGS1_CANONICALIZED_PATHS,
84 flags2=smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES,
85 negoData='\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00'):
86 """
87 Perform protocol negotiation
89 :param string preferredDialect: the dialect desired to talk with the target server. If None is specified the highest one available will be used
90 :param string flags1: the SMB FLAGS capabilities
91 :param string flags2: the SMB FLAGS2 capabilities
92 :param string negoData: data to be sent as part of the nego handshake
94 :return: True
95 :raise SessionError: if error
96 """
98 # If port 445 and the name sent is *SMBSERVER we're setting the name to the IP. This is to help some old
99 # applications still believing
100 # *SMSBSERVER will work against modern OSes. If port is NETBIOS_SESSION_PORT the user better know about i
101 # *SMBSERVER's limitations
102 if self._sess_port == nmb.SMB_SESSION_PORT and self._remoteName == '*SMBSERVER':
103 self._remoteName = self._remoteHost
104 elif self._sess_port == nmb.NETBIOS_SESSION_PORT and self._remoteName == '*SMBSERVER':
105 # If remote name is *SMBSERVER let's try to query its name.. if can't be guessed, continue and hope for the best
106 nb = nmb.NetBIOS()
107 try:
108 res = nb.getnetbiosname(self._remoteHost)
109 except:
110 pass
111 else:
112 self._remoteName = res
114 if self._sess_port == nmb.NETBIOS_SESSION_PORT:
115 negoData = '\x02NT LM 0.12\x00\x02SMB 2.002\x00'
117 hostType = nmb.TYPE_SERVER
118 if preferredDialect is None:
119 # If no preferredDialect sent, we try the highest available one.
120 packet = self.negotiateSessionWildcard(self._myName, self._remoteName, self._remoteHost, self._sess_port,
121 self._timeout, True, flags1=flags1, flags2=flags2, data=negoData)
122 if packet[0:1] == b'\xfe': 122 ↛ 129line 122 didn't jump to line 129, because the condition on line 122 was never false
123 # Answer is SMB2 packet
124 self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType,
125 self._sess_port, self._timeout, session=self._nmbSession,
126 negSessionResponse=SMB2Packet(packet))
127 else:
128 # Answer is SMB packet, sticking to SMBv1
129 self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType,
130 self._sess_port, self._timeout, session=self._nmbSession,
131 negPacket=packet)
132 else:
133 if preferredDialect == smb.SMB_DIALECT:
134 self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType,
135 self._sess_port, self._timeout)
136 elif preferredDialect in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, SMB2_DIALECT_311]: 136 ↛ 140line 136 didn't jump to line 140, because the condition on line 136 was never false
137 self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType,
138 self._sess_port, self._timeout, preferredDialect=preferredDialect)
139 else:
140 raise Exception("Unknown dialect %s")
142 # propagate flags to the smb sub-object, except for Unicode (if server supports)
143 # does not affect smb3 objects
144 if isinstance(self._SMBConnection, smb.SMB):
145 if self._SMBConnection.get_flags()[1] & smb.SMB.FLAGS2_UNICODE: 145 ↛ 146line 145 didn't jump to line 146, because the condition on line 145 was never true
146 flags2 |= smb.SMB.FLAGS2_UNICODE
147 self._SMBConnection.set_flags(flags1=flags1, flags2=flags2)
149 return True
151 def negotiateSessionWildcard(self, myName, remoteName, remoteHost, sess_port, timeout, extended_security=True, flags1=0,
152 flags2=0, data=None):
153 # Here we follow [MS-SMB2] negotiation handshake trying to understand what dialects
154 # (including SMB1) is supported on the other end.
156 if not myName: 156 ↛ 162line 156 didn't jump to line 162, because the condition on line 156 was never false
157 myName = socket.gethostname()
158 i = myName.find('.')
159 if i > -1: 159 ↛ 160line 159 didn't jump to line 160, because the condition on line 159 was never true
160 myName = myName[:i]
162 tries = 0
163 smbp = smb.NewSMBPacket()
164 smbp['Flags1'] = flags1
165 # FLAGS2_UNICODE is required by some stacks to continue, regardless of subsequent support
166 smbp['Flags2'] = flags2 | smb.SMB.FLAGS2_UNICODE
167 resp = None
168 while tries < 2: 168 ↛ 189line 168 didn't jump to line 189, because the condition on line 168 was never false
169 self._nmbSession = nmb.NetBIOSTCPSession(myName, remoteName, remoteHost, nmb.TYPE_SERVER, sess_port,
170 timeout)
172 negSession = smb.SMBCommand(smb.SMB.SMB_COM_NEGOTIATE)
173 if extended_security is True: 173 ↛ 175line 173 didn't jump to line 175, because the condition on line 173 was never false
174 smbp['Flags2'] |= smb.SMB.FLAGS2_EXTENDED_SECURITY
175 negSession['Data'] = data
176 smbp.addCommand(negSession)
177 self._nmbSession.send_packet(smbp.getData())
179 try:
180 resp = self._nmbSession.recv_packet(timeout)
181 break
182 except nmb.NetBIOSError:
183 # OSX Yosemite asks for more Flags. Let's give it a try and see what happens
184 smbp['Flags2'] |= smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | smb.SMB.FLAGS2_UNICODE
185 smbp['Data'] = []
187 tries += 1
189 if resp is None: 189 ↛ 191line 189 didn't jump to line 191, because the condition on line 189 was never true
190 # No luck, quitting
191 raise Exception('No answer!')
193 return resp.get_trailer()
196 def getNMBServer(self):
197 return self._nmbSession
199 def getSMBServer(self):
200 """
201 returns the SMB/SMB3 instance being used. Useful for calling low level methods
202 """
203 return self._SMBConnection
205 def getDialect(self):
206 return self._SMBConnection.getDialect()
208 def getServerName(self):
209 return self._SMBConnection.get_server_name()
211 def getClientName(self):
212 return self._SMBConnection.get_client_name()
214 def getRemoteHost(self):
215 return self._SMBConnection.get_remote_host()
217 def getRemoteName(self):
218 return self._SMBConnection.get_remote_name()
220 def setRemoteName(self, name):
221 return self._SMBConnection.set_remote_name(name)
223 def getServerDomain(self):
224 return self._SMBConnection.get_server_domain()
226 def getServerDNSDomainName(self):
227 return self._SMBConnection.get_server_dns_domain_name()
229 def getServerDNSHostName(self):
230 return self._SMBConnection.get_server_dns_host_name()
232 def getServerOS(self):
233 return self._SMBConnection.get_server_os()
235 def getServerOSMajor(self):
236 return self._SMBConnection.get_server_os_major()
238 def getServerOSMinor(self):
239 return self._SMBConnection.get_server_os_minor()
241 def getServerOSBuild(self):
242 return self._SMBConnection.get_server_os_build()
244 def doesSupportNTLMv2(self):
245 return self._SMBConnection.doesSupportNTLMv2()
247 def isLoginRequired(self):
248 return self._SMBConnection.is_login_required()
250 def isSigningRequired(self):
251 return self._SMBConnection.is_signing_required()
253 def getCredentials(self):
254 return self._SMBConnection.getCredentials()
256 def getIOCapabilities(self):
257 return self._SMBConnection.getIOCapabilities()
259 def login(self, user, password, domain = '', lmhash = '', nthash = '', ntlmFallback = True):
260 """
261 logins into the target system
263 :param string user: username
264 :param string password: password for the user
265 :param string domain: domain where the account is valid for
266 :param string lmhash: LMHASH used to authenticate using hashes (password is not used)
267 :param string nthash: NTHASH used to authenticate using hashes (password is not used)
268 :param bool ntlmFallback: If True it will try NTLMv1 authentication if NTLMv2 fails. Only available for SMBv1
270 :return: None
271 :raise SessionError: if error
272 """
273 self._ntlmFallback = ntlmFallback
274 try:
275 if self.getDialect() == smb.SMB_DIALECT:
276 return self._SMBConnection.login(user, password, domain, lmhash, nthash, ntlmFallback)
277 else:
278 return self._SMBConnection.login(user, password, domain, lmhash, nthash)
279 except (smb.SessionError, smb3.SessionError) as e:
280 raise SessionError(e.get_error_code(), e.get_error_packet())
282 def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None,
283 TGS=None, useCache=True):
284 """
285 logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported.
287 :param string user: username
288 :param string password: password for the user
289 :param string domain: domain where the account is valid for (required)
290 :param string lmhash: LMHASH used to authenticate using hashes (password is not used)
291 :param string nthash: NTHASH used to authenticate using hashes (password is not used)
292 :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication
293 :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho)
294 :param struct TGT: If there's a TGT available, send the structure here and it will be used
295 :param struct TGS: same for TGS. See smb3.py for the format
296 :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False
298 :return: None
299 :raise SessionError: if error
300 """
301 import os
302 from impacket.krb5.ccache import CCache
303 from impacket.krb5.kerberosv5 import KerberosError
304 from impacket.krb5 import constants
306 self._kdcHost = kdcHost
307 self._useCache = useCache
309 if TGT is not None or TGS is not None: 309 ↛ 310line 309 didn't jump to line 310, because the condition on line 309 was never true
310 useCache = False
312 if useCache is True: 312 ↛ 348line 312 didn't jump to line 348, because the condition on line 312 was never false
313 try:
314 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
315 except:
316 # No cache present
317 pass
318 else:
319 LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME'))
320 # retrieve domain information from CCache file if needed
321 if domain == '':
322 domain = ccache.principal.realm['data'].decode('utf-8')
323 LOG.debug('Domain retrieved from CCache: %s' % domain)
325 principal = 'cifs/%s@%s' % (self.getRemoteName().upper(), domain.upper())
326 creds = ccache.getCredential(principal)
327 if creds is None:
328 # Let's try for the TGT and go from there
329 principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper())
330 creds = ccache.getCredential(principal)
331 if creds is not None:
332 TGT = creds.toTGT()
333 LOG.debug('Using TGT from cache')
334 else:
335 LOG.debug("No valid credentials found in cache. ")
336 else:
337 TGS = creds.toTGS(principal)
338 LOG.debug('Using TGS from cache')
340 # retrieve user information from CCache file if needed
341 if user == '' and creds is not None:
342 user = creds['client'].prettyPrint().split(b'@')[0].decode('utf-8')
343 LOG.debug('Username retrieved from CCache: %s' % user)
344 elif user == '' and len(ccache.principal.components) > 0:
345 user = ccache.principal.components[0]['data'].decode('utf-8')
346 LOG.debug('Username retrieved from CCache: %s' % user)
348 while True:
349 try:
350 if self.getDialect() == smb.SMB_DIALECT:
351 return self._SMBConnection.kerberos_login(user, password, domain, lmhash, nthash, aesKey, kdcHost,
352 TGT, TGS)
353 return self._SMBConnection.kerberosLogin(user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT,
354 TGS)
355 except (smb.SessionError, smb3.SessionError) as e:
356 raise SessionError(e.get_error_code(), e.get_error_packet())
357 except KerberosError as e:
358 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
359 # We might face this if the target does not support AES
360 # So, if that's the case we'll force using RC4 by converting
361 # the password to lm/nt hashes and hope for the best. If that's already
362 # done, byebye.
363 if lmhash == '' and nthash == '' and (aesKey == '' or aesKey is None) and TGT is None and TGS is None:
364 lmhash = compute_lmhash(password)
365 nthash = compute_nthash(password)
366 else:
367 raise e
368 else:
369 raise e
371 def isGuestSession(self):
372 try:
373 return self._SMBConnection.isGuestSession()
374 except (smb.SessionError, smb3.SessionError) as e:
375 raise SessionError(e.get_error_code(), e.get_error_packet())
377 def logoff(self):
378 try:
379 return self._SMBConnection.logoff()
380 except (smb.SessionError, smb3.SessionError) as e:
381 raise SessionError(e.get_error_code(), e.get_error_packet())
384 def connectTree(self,share):
385 if self.getDialect() == smb.SMB_DIALECT:
386 # If we already have a UNC we do nothing.
387 if ntpath.ismount(share) is False:
388 # Else we build it
389 share = ntpath.basename(share)
390 share = '\\\\' + self.getRemoteHost() + '\\' + share
391 try:
392 return self._SMBConnection.connect_tree(share)
393 except (smb.SessionError, smb3.SessionError) as e:
394 raise SessionError(e.get_error_code(), e.get_error_packet())
397 def disconnectTree(self, treeId):
398 try:
399 return self._SMBConnection.disconnect_tree(treeId)
400 except (smb.SessionError, smb3.SessionError) as e:
401 raise SessionError(e.get_error_code(), e.get_error_packet())
404 def listShares(self):
405 """
406 get a list of available shares at the connected target
408 :return: a list containing dict entries for each share
409 :raise SessionError: if error
410 """
411 # Get the shares through RPC
412 from impacket.dcerpc.v5 import transport, srvs
413 rpctransport = transport.SMBTransport(self.getRemoteName(), self.getRemoteHost(), filename=r'\srvsvc',
414 smb_connection=self)
415 dce = rpctransport.get_dce_rpc()
416 dce.connect()
417 dce.bind(srvs.MSRPC_UUID_SRVS)
418 resp = srvs.hNetrShareEnum(dce, 1)
419 return resp['InfoStruct']['ShareInfo']['Level1']['Buffer']
421 def listPath(self, shareName, path, password = None):
422 """
423 list the files/directories under shareName/path
425 :param string shareName: a valid name for the share where the files/directories are going to be searched
426 :param string path: a base path relative to shareName
427 :param string password: the password for the share
429 :return: a list containing smb.SharedFile items
430 :raise SessionError: if error
431 """
433 try:
434 return self._SMBConnection.list_path(shareName, path, password)
435 except (smb.SessionError, smb3.SessionError) as e:
436 raise SessionError(e.get_error_code(), e.get_error_packet())
438 def createFile(self, treeId, pathName, desiredAccess=GENERIC_ALL,
439 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
440 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OVERWRITE_IF,
441 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
442 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
443 """
444 Creates a remote file
446 :param HANDLE treeId: a valid handle for the share where the file is to be created
447 :param string pathName: the path name of the file to create
448 :param int desiredAccess: The level of access that is required, as specified in https://msdn.microsoft.com/en-us/library/cc246503.aspx
449 :param int shareMode: Specifies the sharing mode for the open.
450 :param int creationOption: Specifies the options to be applied when creating or opening the file.
451 :param int creationDisposition: Defines the action the server MUST take if the file that is specified in the name
452 field already exists.
453 :param int fileAttributes: This field MUST be a combination of the values specified in [MS-FSCC] section 2.6, and MUST NOT include any values other than those specified in that section.
454 :param int impersonationLevel: This field specifies the impersonation level requested by the application that is issuing the create request.
455 :param int securityFlags: This field MUST NOT be used and MUST be reserved. The client MUST set this to 0, and the server MUST ignore it.
456 :param int oplockLevel: The requested oplock level
457 :param createContexts: A variable-length attribute that is sent with an SMB2 CREATE Request or SMB2 CREATE Response that either gives extra information about how the create will be processed, or returns extra information about how the create was processed.
459 :return: a valid file descriptor
460 :raise SessionError: if error
461 """
462 if self.getDialect() == smb.SMB_DIALECT:
463 _, flags2 = self._SMBConnection.get_flags()
465 pathName = pathName.replace('/', '\\')
466 packetPathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName
468 ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)
469 ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters()
470 ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2)
471 ntCreate['Parameters']['FileNameLength']= len(packetPathName)
472 ntCreate['Parameters']['AccessMask'] = desiredAccess
473 ntCreate['Parameters']['FileAttributes']= fileAttributes
474 ntCreate['Parameters']['ShareAccess'] = shareMode
475 ntCreate['Parameters']['Disposition'] = creationDisposition
476 ntCreate['Parameters']['CreateOptions'] = creationOption
477 ntCreate['Parameters']['Impersonation'] = impersonationLevel
478 ntCreate['Parameters']['SecurityFlags'] = securityFlags
479 ntCreate['Parameters']['CreateFlags'] = 0x16
480 ntCreate['Data']['FileName'] = packetPathName
482 if flags2 & smb.SMB.FLAGS2_UNICODE:
483 ntCreate['Data']['Pad'] = 0x0
485 if createContexts is not None: 485 ↛ 486line 485 didn't jump to line 486, because the condition on line 485 was never true
486 LOG.error("CreateContexts not supported in SMB1")
488 try:
489 return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate)
490 except (smb.SessionError, smb3.SessionError) as e:
491 raise SessionError(e.get_error_code(), e.get_error_packet())
492 else:
493 try:
494 return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption,
495 creationDisposition, fileAttributes, impersonationLevel,
496 securityFlags, oplockLevel, createContexts)
497 except (smb.SessionError, smb3.SessionError) as e:
498 raise SessionError(e.get_error_code(), e.get_error_packet())
500 def openFile(self, treeId, pathName, desiredAccess=FILE_READ_DATA | FILE_WRITE_DATA, shareMode=FILE_SHARE_READ,
501 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OPEN,
502 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
503 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
504 """
505 opens a remote file
507 :param HANDLE treeId: a valid handle for the share where the file is to be opened
508 :param string pathName: the path name to open
509 :param int desiredAccess: The level of access that is required, as specified in https://msdn.microsoft.com/en-us/library/cc246503.aspx
510 :param int shareMode: Specifies the sharing mode for the open.
511 :param int creationOption: Specifies the options to be applied when creating or opening the file.
512 :param int creationDisposition: Defines the action the server MUST take if the file that is specified in the name
513 field already exists.
514 :param int fileAttributes: This field MUST be a combination of the values specified in [MS-FSCC] section 2.6, and MUST NOT include any values other than those specified in that section.
515 :param int impersonationLevel: This field specifies the impersonation level requested by the application that is issuing the create request.
516 :param int securityFlags: This field MUST NOT be used and MUST be reserved. The client MUST set this to 0, and the server MUST ignore it.
517 :param int oplockLevel: The requested oplock level
518 :param createContexts: A variable-length attribute that is sent with an SMB2 CREATE Request or SMB2 CREATE Response that either gives extra information about how the create will be processed, or returns extra information about how the create was processed.
520 :return: a valid file descriptor
521 :raise SessionError: if error
522 """
524 if self.getDialect() == smb.SMB_DIALECT:
525 _, flags2 = self._SMBConnection.get_flags()
527 pathName = pathName.replace('/', '\\')
528 packetPathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName
530 ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)
531 ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters()
532 ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2)
533 ntCreate['Parameters']['FileNameLength']= len(packetPathName)
534 ntCreate['Parameters']['AccessMask'] = desiredAccess
535 ntCreate['Parameters']['FileAttributes']= fileAttributes
536 ntCreate['Parameters']['ShareAccess'] = shareMode
537 ntCreate['Parameters']['Disposition'] = creationDisposition
538 ntCreate['Parameters']['CreateOptions'] = creationOption
539 ntCreate['Parameters']['Impersonation'] = impersonationLevel
540 ntCreate['Parameters']['SecurityFlags'] = securityFlags
541 ntCreate['Parameters']['CreateFlags'] = 0x16
542 ntCreate['Data']['FileName'] = packetPathName
544 if flags2 & smb.SMB.FLAGS2_UNICODE:
545 ntCreate['Data']['Pad'] = 0x0
547 if createContexts is not None: 547 ↛ 548line 547 didn't jump to line 548, because the condition on line 547 was never true
548 LOG.error("CreateContexts not supported in SMB1")
550 try:
551 return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate)
552 except (smb.SessionError, smb3.SessionError) as e:
553 raise SessionError(e.get_error_code(), e.get_error_packet())
554 else:
555 try:
556 return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption,
557 creationDisposition, fileAttributes, impersonationLevel,
558 securityFlags, oplockLevel, createContexts)
559 except (smb.SessionError, smb3.SessionError) as e:
560 raise SessionError(e.get_error_code(), e.get_error_packet())
562 def writeFile(self, treeId, fileId, data, offset=0):
563 """
564 writes data to a file
566 :param HANDLE treeId: a valid handle for the share where the file is to be written
567 :param HANDLE fileId: a valid handle for the file
568 :param string data: buffer with the data to write
569 :param integer offset: offset where to start writing the data
571 :return: amount of bytes written
572 :raise SessionError: if error
573 """
574 try:
575 return self._SMBConnection.writeFile(treeId, fileId, data, offset)
576 except (smb.SessionError, smb3.SessionError) as e:
577 raise SessionError(e.get_error_code(), e.get_error_packet())
579 def readFile(self, treeId, fileId, offset = 0, bytesToRead = None, singleCall = True):
580 """
581 reads data from a file
583 :param HANDLE treeId: a valid handle for the share where the file is to be read
584 :param HANDLE fileId: a valid handle for the file to be read
585 :param integer offset: offset where to start reading the data
586 :param integer bytesToRead: amount of bytes to attempt reading. If None, it will attempt to read Dialect['MaxBufferSize'] bytes.
587 :param boolean singleCall: If True it won't attempt to read all bytesToRead. It will only make a single read call
589 :return: the data read. Length of data read is not always bytesToRead
590 :raise SessionError: if error
591 """
592 finished = False
593 data = b''
594 maxReadSize = self._SMBConnection.getIOCapabilities()['MaxReadSize']
595 if bytesToRead is None:
596 bytesToRead = maxReadSize
597 remainingBytesToRead = bytesToRead
598 while not finished:
599 if remainingBytesToRead > maxReadSize:
600 toRead = maxReadSize
601 else:
602 toRead = remainingBytesToRead
603 try:
604 bytesRead = self._SMBConnection.read_andx(treeId, fileId, offset, toRead)
605 except (smb.SessionError, smb3.SessionError) as e:
606 if e.get_error_code() == nt_errors.STATUS_END_OF_FILE:
607 toRead = b''
608 break
609 else:
610 raise SessionError(e.get_error_code(), e.get_error_packet())
612 data += bytesRead
613 if len(data) >= bytesToRead:
614 finished = True
615 elif len(bytesRead) == 0: 615 ↛ 617line 615 didn't jump to line 617, because the condition on line 615 was never true
616 # End of the file achieved.
617 finished = True
618 elif singleCall is True: 618 ↛ 621line 618 didn't jump to line 621, because the condition on line 618 was never false
619 finished = True
620 else:
621 offset += len(bytesRead)
622 remainingBytesToRead -= len(bytesRead)
624 return data
626 def closeFile(self, treeId, fileId):
627 """
628 closes a file handle
630 :param HANDLE treeId: a valid handle for the share where the file is to be opened
631 :param HANDLE fileId: a valid handle for the file/directory to be closed
633 :return: None
634 :raise SessionError: if error
635 """
636 try:
637 return self._SMBConnection.close(treeId, fileId)
638 except (smb.SessionError, smb3.SessionError) as e:
639 raise SessionError(e.get_error_code(), e.get_error_packet())
641 def deleteFile(self, shareName, pathName):
642 """
643 removes a file
645 :param string shareName: a valid name for the share where the file is to be deleted
646 :param string pathName: the path name to remove
648 :return: None
649 :raise SessionError: if error
650 """
651 try:
652 return self._SMBConnection.remove(shareName, pathName)
653 except (smb.SessionError, smb3.SessionError) as e:
654 raise SessionError(e.get_error_code(), e.get_error_packet())
656 def queryInfo(self, treeId, fileId):
657 """
658 queries basic information about an opened file/directory
660 :param HANDLE treeId: a valid handle for the share where the file is to be queried
661 :param HANDLE fileId: a valid handle for the file/directory to be queried
663 :return: a smb.SMBQueryFileStandardInfo structure.
664 :raise SessionError: if error
665 """
666 try:
667 if self.getDialect() == smb.SMB_DIALECT:
668 res = self._SMBConnection.query_file_info(treeId, fileId)
669 else:
670 res = self._SMBConnection.queryInfo(treeId, fileId)
671 return smb.SMBQueryFileStandardInfo(res)
672 except (smb.SessionError, smb3.SessionError) as e:
673 raise SessionError(e.get_error_code(), e.get_error_packet())
675 def createDirectory(self, shareName, pathName ):
676 """
677 creates a directory
679 :param string shareName: a valid name for the share where the directory is to be created
680 :param string pathName: the path name or the directory to create
682 :return: None
683 :raise SessionError: if error
684 """
685 try:
686 return self._SMBConnection.mkdir(shareName, pathName)
687 except (smb.SessionError, smb3.SessionError) as e:
688 raise SessionError(e.get_error_code(), e.get_error_packet())
690 def deleteDirectory(self, shareName, pathName):
691 """
692 deletes a directory
694 :param string shareName: a valid name for the share where directory is to be deleted
695 :param string pathName: the path name or the directory to delete
697 :return: None
698 :raise SessionError: if error
699 """
700 try:
701 return self._SMBConnection.rmdir(shareName, pathName)
702 except (smb.SessionError, smb3.SessionError) as e:
703 raise SessionError(e.get_error_code(), e.get_error_packet())
705 def waitNamedPipe(self, treeId, pipeName, timeout = 5):
706 """
707 waits for a named pipe
709 :param HANDLE treeId: a valid handle for the share where the pipe is
710 :param string pipeName: the pipe name to check
711 :param integer timeout: time to wait for an answer
713 :return: None
714 :raise SessionError: if error
715 """
716 try:
717 return self._SMBConnection.waitNamedPipe(treeId, pipeName, timeout = timeout)
718 except (smb.SessionError, smb3.SessionError) as e:
719 raise SessionError(e.get_error_code(), e.get_error_packet())
721 def transactNamedPipe(self, treeId, fileId, data, waitAnswer = True):
722 """
723 writes to a named pipe using a transaction command
725 :param HANDLE treeId: a valid handle for the share where the pipe is
726 :param HANDLE fileId: a valid handle for the pipe
727 :param string data: buffer with the data to write
728 :param boolean waitAnswer: whether or not to wait for an answer
730 :return: None
731 :raise SessionError: if error
732 """
733 try:
734 return self._SMBConnection.TransactNamedPipe(treeId, fileId, data, waitAnswer = waitAnswer)
735 except (smb.SessionError, smb3.SessionError) as e:
736 raise SessionError(e.get_error_code(), e.get_error_packet())
738 def transactNamedPipeRecv(self):
739 """
740 reads from a named pipe using a transaction command
742 :return: data read
743 :raise SessionError: if error
744 """
745 try:
746 return self._SMBConnection.TransactNamedPipeRecv()
747 except (smb.SessionError, smb3.SessionError) as e:
748 raise SessionError(e.get_error_code(), e.get_error_packet())
750 def writeNamedPipe(self, treeId, fileId, data, waitAnswer = True):
751 """
752 writes to a named pipe
754 :param HANDLE treeId: a valid handle for the share where the pipe is
755 :param HANDLE fileId: a valid handle for the pipe
756 :param string data: buffer with the data to write
757 :param boolean waitAnswer: whether or not to wait for an answer
759 :return: None
760 :raise SessionError: if error
761 """
762 try:
763 if self.getDialect() == smb.SMB_DIALECT:
764 return self._SMBConnection.write_andx(treeId, fileId, data, wait_answer = waitAnswer, write_pipe_mode = True)
765 else:
766 return self.writeFile(treeId, fileId, data, 0)
767 except (smb.SessionError, smb3.SessionError) as e:
768 raise SessionError(e.get_error_code(), e.get_error_packet())
770 def readNamedPipe(self,treeId, fileId, bytesToRead = None ):
771 """
772 read from a named pipe
774 :param HANDLE treeId: a valid handle for the share where the pipe resides
775 :param HANDLE fileId: a valid handle for the pipe
776 :param integer bytesToRead: amount of data to read
778 :return: None
779 :raise SessionError: if error
780 """
782 try:
783 return self.readFile(treeId, fileId, bytesToRead = bytesToRead, singleCall = True)
784 except (smb.SessionError, smb3.SessionError) as e:
785 raise SessionError(e.get_error_code(), e.get_error_packet())
788 def getFile(self, shareName, pathName, callback, shareAccessMode = None):
789 """
790 downloads a file
792 :param string shareName: name for the share where the file is to be retrieved
793 :param string pathName: the path name to retrieve
794 :param callback callback: function called to write the contents read.
795 :param int shareAccessMode:
797 :return: None
798 :raise SessionError: if error
799 """
800 try:
801 if shareAccessMode is None: 801 ↛ 805line 801 didn't jump to line 805, because the condition on line 801 was never false
802 # if share access mode is none, let's the underlying API deals with it
803 return self._SMBConnection.retr_file(shareName, pathName, callback)
804 else:
805 return self._SMBConnection.retr_file(shareName, pathName, callback, shareAccessMode=shareAccessMode)
806 except (smb.SessionError, smb3.SessionError) as e:
807 raise SessionError(e.get_error_code(), e.get_error_packet())
809 def putFile(self, shareName, pathName, callback, shareAccessMode = None):
810 """
811 uploads a file
813 :param string shareName: name for the share where the file is to be uploaded
814 :param string pathName: the path name to upload
815 :param callback callback: function called to read the contents to be written.
816 :param int shareAccessMode:
818 :return: None
819 :raise SessionError: if error
820 """
821 try:
822 if shareAccessMode is None: 822 ↛ 826line 822 didn't jump to line 826, because the condition on line 822 was never false
823 # if share access mode is none, let's the underlying API deals with it
824 return self._SMBConnection.stor_file(shareName, pathName, callback)
825 else:
826 return self._SMBConnection.stor_file(shareName, pathName, callback, shareAccessMode)
827 except (smb.SessionError, smb3.SessionError) as e:
828 raise SessionError(e.get_error_code(), e.get_error_packet())
830 def listSnapshots(self, tid, path):
831 """
832 lists the snapshots for the given directory
834 :param int tid: tree id of current connection
835 :param string path: directory to list the snapshots of
837 :raise SessionError: if error
838 """
840 # Verify we're under SMB2+ session
841 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:
842 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)
844 fid = self.openFile(tid, path, FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE,
845 fileAttributes=None, creationOption=FILE_SYNCHRONOUS_IO_NONALERT,
846 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE)
848 # first send with maxOutputResponse=16 to get the required size
849 try:
850 snapshotData = SRV_SNAPSHOT_ARRAY(self._SMBConnection.ioctl(tid, fid, FSCTL_SRV_ENUMERATE_SNAPSHOTS,
851 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=16))
852 except (smb.SessionError, smb3.SessionError) as e:
853 self.closeFile(tid, fid)
854 raise SessionError(e.get_error_code(), e.get_error_packet())
856 if snapshotData['SnapShotArraySize'] >= 52:
857 # now send an appropriate sized buffer
858 try:
859 snapshotData = SRV_SNAPSHOT_ARRAY(self._SMBConnection.ioctl(tid, fid, FSCTL_SRV_ENUMERATE_SNAPSHOTS,
860 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=snapshotData['SnapShotArraySize']+12))
861 except (smb.SessionError, smb3.SessionError) as e:
862 self.closeFile(tid, fid)
863 raise SessionError(e.get_error_code(), e.get_error_packet())
865 self.closeFile(tid, fid)
866 return list(filter(None, snapshotData['SnapShots'].decode('utf16').split('\x00')))
868 def createMountPoint(self, tid, path, target):
869 """
870 creates a mount point at an existing directory
872 :param int tid: tree id of current connection
873 :param string path: directory at which to create mount point (must already exist)
874 :param string target: target address of mount point
876 :raise SessionError: if error
877 """
879 # Verify we're under SMB2+ session
880 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:
881 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)
883 fid = self.openFile(tid, path, GENERIC_READ | GENERIC_WRITE,
884 creationOption=FILE_OPEN_REPARSE_POINT)
886 if target.startswith("\\"):
887 fixed_name = target.encode('utf-16le')
888 else:
889 fixed_name = ("\\??\\" + target).encode('utf-16le')
891 name = target.encode('utf-16le')
893 reparseData = MOUNT_POINT_REPARSE_DATA_STRUCTURE()
895 reparseData['PathBuffer'] = fixed_name + b"\x00\x00" + name + b"\x00\x00"
896 reparseData['SubstituteNameLength'] = len(fixed_name)
897 reparseData['PrintNameOffset'] = len(fixed_name) + 2
898 reparseData['PrintNameLength'] = len(name)
900 self._SMBConnection.ioctl(tid, fid, FSCTL_SET_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL,
901 inputBlob=reparseData)
903 self.closeFile(tid, fid)
905 def removeMountPoint(self, tid, path):
906 """
907 removes a mount point without deleting the underlying directory
909 :param int tid: tree id of current connection
910 :param string path: path to mount point to remove
912 :raise SessionError: if error
913 """
915 # Verify we're under SMB2+ session
916 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:
917 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)
919 fid = self.openFile(tid, path, GENERIC_READ | GENERIC_WRITE,
920 creationOption=FILE_OPEN_REPARSE_POINT)
922 reparseData = MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE()
924 reparseData['DataBuffer'] = b""
926 try:
927 self._SMBConnection.ioctl(tid, fid, FSCTL_DELETE_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL,
928 inputBlob=reparseData)
929 except (smb.SessionError, smb3.SessionError) as e:
930 self.closeFile(tid, fid)
931 raise SessionError(e.get_error_code(), e.get_error_packet())
933 self.closeFile(tid, fid)
935 def rename(self, shareName, oldPath, newPath):
936 """
937 renames a file/directory
939 :param string shareName: name for the share where the files/directories are
940 :param string oldPath: the old path name or the directory/file to rename
941 :param string newPath: the new path name or the directory/file to rename
943 :return: True
944 :raise SessionError: if error
945 """
947 try:
948 return self._SMBConnection.rename(shareName, oldPath, newPath)
949 except (smb.SessionError, smb3.SessionError) as e:
950 raise SessionError(e.get_error_code(), e.get_error_packet())
952 def reconnect(self):
953 """
954 reconnects the SMB object based on the original options and credentials used. Only exception is that
955 manualNegotiate will not be honored.
956 Not only the connection will be created but also a login attempt using the original credentials and
957 method (Kerberos, PtH, etc)
959 :return: True
960 :raise SessionError: if error
961 """
962 userName, password, domain, lmhash, nthash, aesKey, TGT, TGS = self.getCredentials()
963 self.negotiateSession(self._preferredDialect)
964 if self._doKerberos is True: 964 ↛ 965line 964 didn't jump to line 965, because the condition on line 964 was never true
965 self.kerberosLogin(userName, password, domain, lmhash, nthash, aesKey, self._kdcHost, TGT, TGS, self._useCache)
966 else:
967 self.login(userName, password, domain, lmhash, nthash, self._ntlmFallback)
969 return True
971 def setTimeout(self, timeout):
972 try:
973 return self._SMBConnection.set_timeout(timeout)
974 except (smb.SessionError, smb3.SessionError) as e:
975 raise SessionError(e.get_error_code(), e.get_error_packet())
977 def getSessionKey(self):
978 if self.getDialect() == smb.SMB_DIALECT:
979 return self._SMBConnection.get_session_key()
980 else:
981 return self._SMBConnection.getSessionKey()
983 def setSessionKey(self, key):
984 if self.getDialect() == smb.SMB_DIALECT:
985 return self._SMBConnection.set_session_key(key)
986 else:
987 return self._SMBConnection.setSessionKey(key)
989 def setHostnameValidation(self, validate, accept_empty, hostname):
990 return self._SMBConnection.set_hostname_validation(validate, accept_empty, hostname)
992 def close(self):
993 """
994 logs off and closes the underlying _NetBIOSSession()
996 :return: None
997 """
998 try:
999 self.logoff()
1000 except:
1001 pass
1002 self._SMBConnection.close_session()
1005class SessionError(Exception):
1006 """
1007 This is the exception every client should catch regardless of the underlying
1008 SMB version used. We'll take care of that. NETBIOS exceptions are NOT included,
1009 since all SMB versions share the same NETBIOS instances.
1010 """
1011 def __init__( self, error = 0, packet=0):
1012 Exception.__init__(self)
1013 self.error = error
1014 self.packet = packet
1016 def getErrorCode( self ):
1017 return self.error
1019 def getErrorPacket( self ):
1020 return self.packet
1022 def getErrorString( self ):
1023 return nt_errors.ERROR_MESSAGES[self.error]
1025 def __str__( self ):
1026 if self.error in nt_errors.ERROR_MESSAGES: 1026 ↛ 1029line 1026 didn't jump to line 1029, because the condition on line 1026 was never false
1027 return 'SMB SessionError: %s(%s)' % (nt_errors.ERROR_MESSAGES[self.error])
1028 else:
1029 return 'SMB SessionError: 0x%x' % self.error