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

# Coverage for /root/GitHubProjects/impacket/impacket/smbconnection.py : 61%

## 475 statements

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#

16

17 import ntpath

18 import socket

19

20 from impacket import smb, smb3, nmb, nt_errors, LOG

21 from impacket.ntlm import compute_lmhash, compute_nthash

22 from 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

28

29

30# So the user doesn't need to import smb, the smb3 are already in here

31 SMB_DIALECT = smb.SMB_DIALECT

32

33 class SMBConnection:

34 """

35 SMBConnection class

36

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

46

47 :return: a SMBConnection instance, if not raises a SessionError exception

48 """

49

50 def __init__(self, remoteName='', remoteHost='', myName=None, sess_port=nmb.SMB_SESSION_PORT, timeout=60, preferredDialect=None,

51 existingConnection=None, manualNegotiate=False):

52

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

68

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

76

77 ##preferredDialect = smb.SMB_DIALECT

78

79 if manualNegotiate is False:

80 self.negotiateSession(preferredDialect)

81

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

88

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

93

94 :return: True

95 :raise SessionError: if error

96 """

97

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

113

114 if self._sess_port == nmb.NETBIOS_SESSION_PORT:

115 negoData = '\x02NT LM 0.12\x00\x02SMB 2.002\x00'

116

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")

141

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)

148

149 return True

150

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.

155

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]

161

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)

171

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())

178

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'] = []

186

187 tries += 1

188

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!')

192

193 return resp.get_trailer()

194

195

196 def getNMBServer(self):

197 return self._nmbSession

198

199 def getSMBServer(self):

200 """

201 returns the SMB/SMB3 instance being used. Useful for calling low level methods

202 """

203 return self._SMBConnection

204

205 def getDialect(self):

206 return self._SMBConnection.getDialect()

207

208 def getServerName(self):

209 return self._SMBConnection.get_server_name()

210

211 def getClientName(self):

212 return self._SMBConnection.get_client_name()

213

214 def getRemoteHost(self):

215 return self._SMBConnection.get_remote_host()

216

217 def getRemoteName(self):

218 return self._SMBConnection.get_remote_name()

219

220 def setRemoteName(self, name):

221 return self._SMBConnection.set_remote_name(name)

222

223 def getServerDomain(self):

224 return self._SMBConnection.get_server_domain()

225

226 def getServerDNSDomainName(self):

227 return self._SMBConnection.get_server_dns_domain_name()

228

229 def getServerDNSHostName(self):

230 return self._SMBConnection.get_server_dns_host_name()

231

232 def getServerOS(self):

233 return self._SMBConnection.get_server_os()

234

235 def getServerOSMajor(self):

236 return self._SMBConnection.get_server_os_major()

237

238 def getServerOSMinor(self):

239 return self._SMBConnection.get_server_os_minor()

240

241 def getServerOSBuild(self):

242 return self._SMBConnection.get_server_os_build()

243

244 def doesSupportNTLMv2(self):

245 return self._SMBConnection.doesSupportNTLMv2()

246

247 def isLoginRequired(self):

248 return self._SMBConnection.is_login_required()

249

250 def isSigningRequired(self):

251 return self._SMBConnection.is_signing_required()

252

253 def getCredentials(self):

254 return self._SMBConnection.getCredentials()

255

256 def getIOCapabilities(self):

257 return self._SMBConnection.getIOCapabilities()

258

259 def login(self, user, password, domain = '', lmhash = '', nthash = '', ntlmFallback = True):

260 """

261 logins into the target system

262

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

269

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())

281

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.

286

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

297

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

305

306 self._kdcHost = kdcHost

307 self._useCache = useCache

308

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

311

312 if useCache is True: 312 ↛ 349line 312 didn't jump to line 349, 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)

324

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')

339

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)

347

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

370

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())

376

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())

382

383

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())

395

396

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())

402

403

404 def listShares(self):

405 """

406 get a list of available shares at the connected target

407

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']

420

421 def listPath(self, shareName, path, password = None):

422 """

423 list the files/directories under shareName/path

424

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

428

429 :return: a list containing smb.SharedFile items

430 :raise SessionError: if error

431 """

432

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())

437

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

445

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.

458

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()

464

465 pathName = pathName.replace('/', '\\')

466 packetPathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName

467

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

481

482 if flags2 & smb.SMB.FLAGS2_UNICODE:

483 ntCreate['Data']['Pad'] = 0x0

484

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")

487

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())

499

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

506

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.

519

520 :return: a valid file descriptor

521 :raise SessionError: if error

522 """

523

524 if self.getDialect() == smb.SMB_DIALECT:

525 _, flags2 = self._SMBConnection.get_flags()

526

527 pathName = pathName.replace('/', '\\')

528 packetPathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName

529

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

543

544 if flags2 & smb.SMB.FLAGS2_UNICODE:

545 ntCreate['Data']['Pad'] = 0x0

546

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")

549

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())

561

562 def writeFile(self, treeId, fileId, data, offset=0):

563 """

564 writes data to a file

565

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

570

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())

578

579 def readFile(self, treeId, fileId, offset = 0, bytesToRead = None, singleCall = True):

580 """

581 reads data from a file

582

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

588

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: 606 ↛ 607line 606 didn't jump to line 607, because the condition on line 606 was never true

607 toRead = b''

608 break

609 else:

610 raise SessionError(e.get_error_code(), e.get_error_packet())

611

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)

623

624 return data

625

626 def closeFile(self, treeId, fileId):

627 """

628 closes a file handle

629

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

632

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())

640

641 def deleteFile(self, shareName, pathName):

642 """

643 removes a file

644

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

647

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())

655

656 def queryInfo(self, treeId, fileId):

657 """

658 queries basic information about an opened file/directory

659

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

662

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())

674

675 def createDirectory(self, shareName, pathName ):

676 """

677 creates a directory

678

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

681

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())

689

690 def deleteDirectory(self, shareName, pathName):

691 """

692 deletes a directory

693

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

696

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())

704

705 def waitNamedPipe(self, treeId, pipeName, timeout = 5):

706 """

707 waits for a named pipe

708

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

712

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())

720

721 def transactNamedPipe(self, treeId, fileId, data, waitAnswer = True):

722 """

723 writes to a named pipe using a transaction command

724

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

729

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())

737

738 def transactNamedPipeRecv(self):

739 """

740 reads from a named pipe using a transaction command

741

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())

749

750 def writeNamedPipe(self, treeId, fileId, data, waitAnswer = True):

751 """

752 writes to a named pipe

753

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

758

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())

769

770 def readNamedPipe(self,treeId, fileId, bytesToRead = None ):

771 """

772 read from a named pipe

773

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

777

778 :return: None

779 :raise SessionError: if error

780 """

781

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())

786

787

788 def getFile(self, shareName, pathName, callback, shareAccessMode = None):

789 """

790 downloads a file

791

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:

796

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())

808

809 def putFile(self, shareName, pathName, callback, shareAccessMode = None):

810 """

811 uploads a file

812

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:

817

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())

829

830 def listSnapshots(self, tid, path):

831 """

832 lists the snapshots for the given directory

833

834 :param int tid: tree id of current connection

835 :param string path: directory to list the snapshots of

836

837 :raise SessionError: if error

838 """

839

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)

843

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)

847

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())

855

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())

864

865 self.closeFile(tid, fid)

866 return list(filter(None, snapshotData['SnapShots'].decode('utf16').split('\x00')))

867

868 def createMountPoint(self, tid, path, target):

869 """

870 creates a mount point at an existing directory

871

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

875

876 :raise SessionError: if error

877 """

878

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)

882

883 fid = self.openFile(tid, path, GENERIC_READ | GENERIC_WRITE,

884 creationOption=FILE_OPEN_REPARSE_POINT)

885

886 if target.startswith("\\"):

887 fixed_name = target.encode('utf-16le')

888 else:

889 fixed_name = ("\\??\\" + target).encode('utf-16le')

890

891 name = target.encode('utf-16le')

892

893 reparseData = MOUNT_POINT_REPARSE_DATA_STRUCTURE()

894

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)

899

900 self._SMBConnection.ioctl(tid, fid, FSCTL_SET_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL,

901 inputBlob=reparseData)

902

903 self.closeFile(tid, fid)

904

905 def removeMountPoint(self, tid, path):

906 """

907 removes a mount point without deleting the underlying directory

908

909 :param int tid: tree id of current connection

910 :param string path: path to mount point to remove

911

912 :raise SessionError: if error

913 """

914

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)

918

919 fid = self.openFile(tid, path, GENERIC_READ | GENERIC_WRITE,

920 creationOption=FILE_OPEN_REPARSE_POINT)

921

922 reparseData = MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE()

923

924 reparseData['DataBuffer'] = b""

925

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())

932

933 self.closeFile(tid, fid)

934

935 def rename(self, shareName, oldPath, newPath):

936 """

937 renames a file/directory

938

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

942

943 :return: True

944 :raise SessionError: if error

945 """

946

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())

951

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)

958

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)

968

969 return True

970

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())

976

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()

982

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)

988

989 def setHostnameValidation(self, validate, accept_empty, hostname):

990 return self._SMBConnection.set_hostname_validation(validate, accept_empty, hostname)

991

992 def close(self):

993 """

994 logs off and closes the underlying _NetBIOSSession()

995

996 :return: None

997 """

998 try:

999 self.logoff()

1000 except:

1001 pass

1002 self._SMBConnection.close_session()

1003

1004

1005 class 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

1015

1016 def getErrorCode( self ):

1017 return self.error

1018

1019 def getErrorPacket( self ):

1020 return self.packet

1021

1022 def getErrorString( self ):

1023 return nt_errors.ERROR_MESSAGES[self.error]

1024

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