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

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

## 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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.

2#

3# This software is provided under under a slightly modified version

4# of the Apache Software License. See the accompanying LICENSE file

5# for more information.

6#

7# Author: Alberto Solino (@agsolino)

8#

9# Description:

10#

11# Wrapper class for SMB1/2/3 so it's transparent for the client.

12# You can still play with the low level methods (version dependent)

13# by calling getSMBServer()

14#

15 import ntpath

16 import socket

17

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

19 from impacket.ntlm import compute_lmhash, compute_nthash

20 from impacket.smb3structs import SMB2Packet, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, GENERIC_ALL, FILE_SHARE_READ, \

21 FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITE_IF, FILE_ATTRIBUTE_NORMAL, \

22 SMB2_IL_IMPERSONATION, SMB2_OPLOCK_LEVEL_NONE, FILE_READ_DATA , FILE_WRITE_DATA, FILE_OPEN, GENERIC_READ, GENERIC_WRITE, \

23 FILE_OPEN_REPARSE_POINT, MOUNT_POINT_REPARSE_DATA_STRUCTURE, FSCTL_SET_REPARSE_POINT, SMB2_0_IOCTL_IS_FSCTL, \

24 MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE, FSCTL_DELETE_REPARSE_POINT, FSCTL_SRV_ENUMERATE_SNAPSHOTS, SRV_SNAPSHOT_ARRAY, \

25 FILE_SYNCHRONOUS_IO_NONALERT, FILE_READ_EA, FILE_READ_ATTRIBUTES, READ_CONTROL, SYNCHRONIZE, SMB2_DIALECT_311

26

27

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

29 SMB_DIALECT = smb.SMB_DIALECT

30

31 class SMBConnection:

32 """

33 SMBConnection class

34

35 :param string remoteName: name of the remote host, can be its NETBIOS name, IP or *\*SMBSERVER*. If the later,

36 and port is 139, the library will try to get the target's server name.

37 :param string remoteHost: target server's remote address (IPv4, IPv6) or FQDN

38 :param string/optional myName: client's NETBIOS name

39 :param integer/optional sess_port: target port to connect

40 :param integer/optional timeout: timeout in seconds when receiving packets

41 :param optional preferredDialect: the dialect desired to talk with the target server. If not specified the highest

42 one available will be used

43 :param optional boolean manualNegotiate: the user manually performs SMB_COM_NEGOTIATE

44

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

46 """

47

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

49 existingConnection=None, manualNegotiate=False):

50

51 self._SMBConnection = 0

52 self._dialect = ''

53 self._nmbSession = 0

54 self._sess_port = sess_port

55 self._myName = myName

56 self._remoteHost = remoteHost

57 self._remoteName = remoteName

58 self._timeout = timeout

59 self._preferredDialect = preferredDialect

60 self._existingConnection = existingConnection

61 self._manualNegotiate = manualNegotiate

62 self._doKerberos = False

63 self._kdcHost = None

64 self._useCache = True

65 self._ntlmFallback = True

66

67 if existingConnection is not None: 67 ↛ 69line 67 didn't jump to line 69, because the condition on line 67 was never true

68 # Existing Connection must be a smb or smb3 instance

69 assert ( isinstance(existingConnection,smb.SMB) or isinstance(existingConnection, smb3.SMB3))

70 self._SMBConnection = existingConnection

71 self._preferredDialect = self._SMBConnection.getDialect()

72 self._doKerberos = self._SMBConnection.getKerberos()

73 return

74

75 ##preferredDialect = smb.SMB_DIALECT

76

77 if manualNegotiate is False:

78 self.negotiateSession(preferredDialect)

79

80 def negotiateSession(self, preferredDialect=None,

81 flags1=smb.SMB.FLAGS1_PATHCASELESS | smb.SMB.FLAGS1_CANONICALIZED_PATHS,

82 flags2=smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES,

83 negoData='\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00'):

84 """

85 Perform protocol negotiation

86

87 :param string preferredDialect: the dialect desired to talk with the target server. If None is specified the highest one available will be used

88 :param string flags1: the SMB FLAGS capabilities

89 :param string flags2: the SMB FLAGS2 capabilities

90 :param string negoData: data to be sent as part of the nego handshake

91

92 :return: True, raises a Session Error if error.

93 """

94

95 # If port 445 and the name sent is *SMBSERVER we're setting the name to the IP. This is to help some old

96 # applications still believing

97 # *SMSBSERVER will work against modern OSes. If port is NETBIOS_SESSION_PORT the user better know about i

98 # *SMBSERVER's limitations

99 if self._sess_port == nmb.SMB_SESSION_PORT and self._remoteName == '*SMBSERVER':

100 self._remoteName = self._remoteHost

101 elif self._sess_port == nmb.NETBIOS_SESSION_PORT and self._remoteName == '*SMBSERVER':

102 # If remote name is *SMBSERVER let's try to query its name.. if can't be guessed, continue and hope for the best

103 nb = nmb.NetBIOS()

104 try:

105 res = nb.getnetbiosname(self._remoteHost)

106 except:

107 pass

108 else:

109 self._remoteName = res

110

111 if self._sess_port == nmb.NETBIOS_SESSION_PORT:

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

113

114 hostType = nmb.TYPE_SERVER

115 if preferredDialect is None:

116 # If no preferredDialect sent, we try the highest available one.

117 packet = self.negotiateSessionWildcard(self._myName, self._remoteName, self._remoteHost, self._sess_port,

118 self._timeout, True, flags1=flags1, flags2=flags2, data=negoData)

119 if packet[0:1] == b'\xfe': 119 ↛ 126line 119 didn't jump to line 126, because the condition on line 119 was never false

120 # Answer is SMB2 packet

121 self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType,

122 self._sess_port, self._timeout, session=self._nmbSession,

123 negSessionResponse=SMB2Packet(packet))

124 else:

125 # Answer is SMB packet, sticking to SMBv1

126 self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType,

127 self._sess_port, self._timeout, session=self._nmbSession,

128 negPacket=packet)

129 else:

130 if preferredDialect == smb.SMB_DIALECT:

131 self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType,

132 self._sess_port, self._timeout)

133 elif preferredDialect in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, SMB2_DIALECT_311]: 133 ↛ 137line 133 didn't jump to line 137, because the condition on line 133 was never false

134 self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType,

135 self._sess_port, self._timeout, preferredDialect=preferredDialect)

136 else:

137 raise Exception("Unknown dialect %s")

138

139 # propagate flags to the smb sub-object, except for Unicode (if server supports)

140 # does not affect smb3 objects

141 if isinstance(self._SMBConnection, smb.SMB):

142 if self._SMBConnection.get_flags()[1] & smb.SMB.FLAGS2_UNICODE: 142 ↛ 143line 142 didn't jump to line 143, because the condition on line 142 was never true

143 flags2 |= smb.SMB.FLAGS2_UNICODE

144 self._SMBConnection.set_flags(flags1=flags1, flags2=flags2)

145

146 return True

147

148 def negotiateSessionWildcard(self, myName, remoteName, remoteHost, sess_port, timeout, extended_security=True, flags1=0,

149 flags2=0, data=None):

150 # Here we follow [MS-SMB2] negotiation handshake trying to understand what dialects

151 # (including SMB1) is supported on the other end.

152

153 if not myName: 153 ↛ 159line 153 didn't jump to line 159, because the condition on line 153 was never false

154 myName = socket.gethostname()

155 i = myName.find('.')

156 if i > -1: 156 ↛ 157line 156 didn't jump to line 157, because the condition on line 156 was never true

157 myName = myName[:i]

158

159 tries = 0

160 smbp = smb.NewSMBPacket()

161 smbp['Flags1'] = flags1

162 # FLAGS2_UNICODE is required by some stacks to continue, regardless of subsequent support

163 smbp['Flags2'] = flags2 | smb.SMB.FLAGS2_UNICODE

164 resp = None

165 while tries < 2: 165 ↛ 186line 165 didn't jump to line 186, because the condition on line 165 was never false

166 self._nmbSession = nmb.NetBIOSTCPSession(myName, remoteName, remoteHost, nmb.TYPE_SERVER, sess_port,

167 timeout)

168

169 negSession = smb.SMBCommand(smb.SMB.SMB_COM_NEGOTIATE)

170 if extended_security is True: 170 ↛ 172line 170 didn't jump to line 172, because the condition on line 170 was never false

171 smbp['Flags2'] |= smb.SMB.FLAGS2_EXTENDED_SECURITY

172 negSession['Data'] = data

173 smbp.addCommand(negSession)

174 self._nmbSession.send_packet(smbp.getData())

175

176 try:

177 resp = self._nmbSession.recv_packet(timeout)

178 break

179 except nmb.NetBIOSError:

180 # OSX Yosemite asks for more Flags. Let's give it a try and see what happens

181 smbp['Flags2'] |= smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | smb.SMB.FLAGS2_UNICODE

182 smbp['Data'] = []

183

184 tries += 1

185

186 if resp is None: 186 ↛ 188line 186 didn't jump to line 188, because the condition on line 186 was never true

187 # No luck, quitting

188 raise Exception('No answer!')

189

190 return resp.get_trailer()

191

192

193 def getNMBServer(self):

194 return self._nmbSession

195

196 def getSMBServer(self):

197 """

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

199 """

200 return self._SMBConnection

201

202 def getDialect(self):

203 return self._SMBConnection.getDialect()

204

205 def getServerName(self):

206 return self._SMBConnection.get_server_name()

207

208 def getClientName(self):

209 return self._SMBConnection.get_client_name()

210

211 def getRemoteHost(self):

212 return self._SMBConnection.get_remote_host()

213

214 def getRemoteName(self):

215 return self._SMBConnection.get_remote_name()

216

217 def setRemoteName(self, name):

218 return self._SMBConnection.set_remote_name(name)

219

220 def getServerDomain(self):

221 return self._SMBConnection.get_server_domain()

222

223 def getServerDNSDomainName(self):

224 return self._SMBConnection.get_server_dns_domain_name()

225

226 def getServerDNSHostName(self):

227 return self._SMBConnection.get_server_dns_host_name()

228

229 def getServerOS(self):

230 return self._SMBConnection.get_server_os()

231

232 def getServerOSMajor(self):

233 return self._SMBConnection.get_server_os_major()

234

235 def getServerOSMinor(self):

236 return self._SMBConnection.get_server_os_minor()

237

238 def getServerOSBuild(self):

239 return self._SMBConnection.get_server_os_build()

240

241 def doesSupportNTLMv2(self):

242 return self._SMBConnection.doesSupportNTLMv2()

243

244 def isLoginRequired(self):

245 return self._SMBConnection.is_login_required()

246

247 def isSigningRequired(self):

248 return self._SMBConnection.is_signing_required()

249

250 def getCredentials(self):

251 return self._SMBConnection.getCredentials()

252

253 def getIOCapabilities(self):

254 return self._SMBConnection.getIOCapabilities()

255

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

257 """

258 logins into the target system

259

260 :param string user: username

261 :param string password: password for the user

262 :param string domain: domain where the account is valid for

263 :param string lmhash: LMHASH used to authenticate using hashes (password is not used)

264 :param string nthash: NTHASH used to authenticate using hashes (password is not used)

265 :param bool ntlmFallback: If True it will try NTLMv1 authentication if NTLMv2 fails. Only available for SMBv1

266

267 :return: None, raises a Session Error if error.

268 """

269 self._ntlmFallback = ntlmFallback

270 try:

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

272 return self._SMBConnection.login(user, password, domain, lmhash, nthash, ntlmFallback)

273 else:

274 return self._SMBConnection.login(user, password, domain, lmhash, nthash)

275 except (smb.SessionError, smb3.SessionError) as e:

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

277

278 def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None,

279 TGS=None, useCache=True):

280 """

281 logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported.

282

283 :param string user: username

284 :param string password: password for the user

285 :param string domain: domain where the account is valid for (required)

286 :param string lmhash: LMHASH used to authenticate using hashes (password is not used)

287 :param string nthash: NTHASH used to authenticate using hashes (password is not used)

288 :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication

289 :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho)

290 :param struct TGT: If there's a TGT available, send the structure here and it will be used

291 :param struct TGS: same for TGS. See smb3.py for the format

292 :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False

293

294 :return: None, raises a Session Error if error.

295 """

296 import os

297 from impacket.krb5.ccache import CCache

298 from impacket.krb5.kerberosv5 import KerberosError

299 from impacket.krb5 import constants

300

301 self._kdcHost = kdcHost

302 self._useCache = useCache

303

304 if TGT is not None or TGS is not None: 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true

305 useCache = False

306

307 if useCache is True: 307 ↛ 344line 307 didn't jump to line 344, because the condition on line 307 was never false

308 try:

309 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))

310 except:

311 # No cache present

312 pass

313 else:

314 LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME'))

315 # retrieve domain information from CCache file if needed

316 if domain == '':

317 domain = ccache.principal.realm['data'].decode('utf-8')

318 LOG.debug('Domain retrieved from CCache: %s' % domain)

319

320 principal = 'cifs/%s@%s' % (self.getRemoteName().upper(), domain.upper())

321 creds = ccache.getCredential(principal)

322 if creds is None:

323 # Let's try for the TGT and go from there

324 principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper())

325 creds = ccache.getCredential(principal)

326 if creds is not None:

327 TGT = creds.toTGT()

328 LOG.debug('Using TGT from cache')

329 else:

330 LOG.debug("No valid credentials found in cache. ")

331 else:

332 TGS = creds.toTGS(principal)

333 LOG.debug('Using TGS from cache')

334

335 # retrieve user information from CCache file if needed

336 if user == '' and creds is not None:

337 user = creds['client'].prettyPrint().split(b'@')[0].decode('utf-8')

338 LOG.debug('Username retrieved from CCache: %s' % user)

339 elif user == '' and len(ccache.principal.components) > 0:

340 user = ccache.principal.components[0]['data'].decode('utf-8')

341 LOG.debug('Username retrieved from CCache: %s' % user)

342

343 while True:

344 try:

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

346 return self._SMBConnection.kerberos_login(user, password, domain, lmhash, nthash, aesKey, kdcHost,

347 TGT, TGS)

348 return self._SMBConnection.kerberosLogin(user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT,

349 TGS)

350 except (smb.SessionError, smb3.SessionError) as e:

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

352 except KerberosError as e:

353 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:

354 # We might face this if the target does not support AES

355 # So, if that's the case we'll force using RC4 by converting

356 # the password to lm/nt hashes and hope for the best. If that's already

357 # done, byebye.

358 if lmhash == '' and nthash == '' and (aesKey == '' or aesKey is None) and TGT is None and TGS is None:

359 lmhash = compute_lmhash(password)

360 nthash = compute_nthash(password)

361 else:

362 raise e

363 else:

364 raise e

365

366 def isGuestSession(self):

367 try:

368 return self._SMBConnection.isGuestSession()

369 except (smb.SessionError, smb3.SessionError) as e:

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

371

372 def logoff(self):

373 try:

374 return self._SMBConnection.logoff()

375 except (smb.SessionError, smb3.SessionError) as e:

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

377

378

379 def connectTree(self,share):

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

381 # If we already have a UNC we do nothing.

382 if ntpath.ismount(share) is False:

383 # Else we build it

384 share = ntpath.basename(share)

385 share = '\\\\' + self.getRemoteHost() + '\\' + share

386 try:

387 return self._SMBConnection.connect_tree(share)

388 except (smb.SessionError, smb3.SessionError) as e:

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

390

391

392 def disconnectTree(self, treeId):

393 try:

394 return self._SMBConnection.disconnect_tree(treeId)

395 except (smb.SessionError, smb3.SessionError) as e:

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

397

398

399 def listShares(self):

400 """

401 get a list of available shares at the connected target

402

403 :return: a list containing dict entries for each share, raises exception if error

404 """

405 # Get the shares through RPC

406 from impacket.dcerpc.v5 import transport, srvs

407 rpctransport = transport.SMBTransport(self.getRemoteName(), self.getRemoteHost(), filename=r'\srvsvc',

408 smb_connection=self)

409 dce = rpctransport.get_dce_rpc()

410 dce.connect()

411 dce.bind(srvs.MSRPC_UUID_SRVS)

412 resp = srvs.hNetrShareEnum(dce, 1)

413 return resp['InfoStruct']['ShareInfo']['Level1']['Buffer']

414

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

416 """

417 list the files/directories under shareName/path

418

419 :param string shareName: a valid name for the share where the files/directories are going to be searched

420 :param string path: a base path relative to shareName

421 :param string password: the password for the share

422

423 :return: a list containing smb.SharedFile items, raises a SessionError exception if error.

424 """

425

426 try:

427 return self._SMBConnection.list_path(shareName, path, password)

428 except (smb.SessionError, smb3.SessionError) as e:

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

430

431 def createFile(self, treeId, pathName, desiredAccess=GENERIC_ALL,

432 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,

433 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OVERWRITE_IF,

434 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,

435 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):

436 """

437 creates a remote file

438

439

440 :param HANDLE treeId: a valid handle for the share where the file is to be created

441 :param string pathName: the path name of the file to create

442 :param int desiredAccess: The level of access that is required, as specified in https://msdn.microsoft.com/en-us/library/cc246503.aspx

443 :param int shareMode: Specifies the sharing mode for the open.

444 :param int creationOption: Specifies the options to be applied when creating or opening the file.

445 :param int creationDisposition: Defines the action the server MUST take if the file that is specified in the name

446 field already exists.

447 :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.

448 :param int impersonationLevel: This field specifies the impersonation level requested by the application that is issuing the create request.

449 :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.

450 :param int oplockLevel: The requested oplock level

451 :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.

452

453 :return: a valid file descriptor, if not raises a SessionError exception.

454 """

455

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

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

458

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

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

461

462 ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)

463 ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters()

464 ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2)

465 ntCreate['Parameters']['FileNameLength']= len(packetPathName)

466 ntCreate['Parameters']['AccessMask'] = desiredAccess

467 ntCreate['Parameters']['FileAttributes']= fileAttributes

468 ntCreate['Parameters']['ShareAccess'] = shareMode

469 ntCreate['Parameters']['Disposition'] = creationDisposition

470 ntCreate['Parameters']['CreateOptions'] = creationOption

471 ntCreate['Parameters']['Impersonation'] = impersonationLevel

472 ntCreate['Parameters']['SecurityFlags'] = securityFlags

473 ntCreate['Parameters']['CreateFlags'] = 0x16

474 ntCreate['Data']['FileName'] = packetPathName

475

476 if flags2 & smb.SMB.FLAGS2_UNICODE:

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

478

479 if createContexts is not None: 479 ↛ 480line 479 didn't jump to line 480, because the condition on line 479 was never true

480 LOG.error("CreateContexts not supported in SMB1")

481

482 try:

483 return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate)

484 except (smb.SessionError, smb3.SessionError) as e:

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

486 else:

487 try:

488 return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption,

489 creationDisposition, fileAttributes, impersonationLevel,

490 securityFlags, oplockLevel, createContexts)

491 except (smb.SessionError, smb3.SessionError) as e:

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

493

494 def openFile(self, treeId, pathName, desiredAccess=FILE_READ_DATA | FILE_WRITE_DATA, shareMode=FILE_SHARE_READ,

495 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OPEN,

496 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,

497 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):

498 """

499 opens a remote file

500

501 :param HANDLE treeId: a valid handle for the share where the file is to be opened

502 :param string pathName: the path name to open

503 :param int desiredAccess: The level of access that is required, as specified in https://msdn.microsoft.com/en-us/library/cc246503.aspx

504 :param int shareMode: Specifies the sharing mode for the open.

505 :param int creationOption: Specifies the options to be applied when creating or opening the file.

506 :param int creationDisposition: Defines the action the server MUST take if the file that is specified in the name

507 field already exists.

508 :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.

509 :param int impersonationLevel: This field specifies the impersonation level requested by the application that is issuing the create request.

510 :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.

511 :param int oplockLevel: The requested oplock level

512 :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.

513

514

515 :return: a valid file descriptor, if not raises a SessionError exception.

516 """

517

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

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

520

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

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

523

524 ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)

525 ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters()

526 ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2)

527 ntCreate['Parameters']['FileNameLength']= len(packetPathName)

528 ntCreate['Parameters']['AccessMask'] = desiredAccess

529 ntCreate['Parameters']['FileAttributes']= fileAttributes

530 ntCreate['Parameters']['ShareAccess'] = shareMode

531 ntCreate['Parameters']['Disposition'] = creationDisposition

532 ntCreate['Parameters']['CreateOptions'] = creationOption

533 ntCreate['Parameters']['Impersonation'] = impersonationLevel

534 ntCreate['Parameters']['SecurityFlags'] = securityFlags

535 ntCreate['Parameters']['CreateFlags'] = 0x16

536 ntCreate['Data']['FileName'] = packetPathName

537

538 if flags2 & smb.SMB.FLAGS2_UNICODE:

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

540

541 if createContexts is not None: 541 ↛ 542line 541 didn't jump to line 542, because the condition on line 541 was never true

542 LOG.error("CreateContexts not supported in SMB1")

543

544 try:

545 return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate)

546 except (smb.SessionError, smb3.SessionError) as e:

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

548 else:

549 try:

550 return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption,

551 creationDisposition, fileAttributes, impersonationLevel,

552 securityFlags, oplockLevel, createContexts)

553 except (smb.SessionError, smb3.SessionError) as e:

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

555

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

557 """

558 writes data to a file

559

560 :param HANDLE treeId: a valid handle for the share where the file is to be written

561 :param HANDLE fileId: a valid handle for the file

562 :param string data: buffer with the data to write

563 :param integer offset: offset where to start writing the data

564

565 :return: amount of bytes written, if not raises a SessionError exception.

566 """

567 try:

568 return self._SMBConnection.writeFile(treeId, fileId, data, offset)

569 except (smb.SessionError, smb3.SessionError) as e:

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

571

572

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

574 """

575 reads data from a file

576

577 :param HANDLE treeId: a valid handle for the share where the file is to be read

578 :param HANDLE fileId: a valid handle for the file to be read

579 :param integer offset: offset where to start reading the data

580 :param integer bytesToRead: amount of bytes to attempt reading. If None, it will attempt to read Dialect['MaxBufferSize'] bytes.

581 :param boolean singleCall: If True it won't attempt to read all bytesToRead. It will only make a single read call

582

583 :return: the data read, if not raises a SessionError exception. Length of data read is not always bytesToRead

584 """

585 finished = False

586 data = b''

587 maxReadSize = self._SMBConnection.getIOCapabilities()['MaxReadSize']

588 if bytesToRead is None:

589 bytesToRead = maxReadSize

590 remainingBytesToRead = bytesToRead

591 while not finished:

592 if remainingBytesToRead > maxReadSize:

593 toRead = maxReadSize

594 else:

595 toRead = remainingBytesToRead

596 try:

597 bytesRead = self._SMBConnection.read_andx(treeId, fileId, offset, toRead)

598 except (smb.SessionError, smb3.SessionError) as e:

599 if e.get_error_code() == nt_errors.STATUS_END_OF_FILE:

600 toRead = b''

601 break

602 else:

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

604

605 data += bytesRead

606 if len(data) >= bytesToRead:

607 finished = True

608 elif len(bytesRead) == 0: 608 ↛ 610line 608 didn't jump to line 610, because the condition on line 608 was never true

609 # End of the file achieved.

610 finished = True

611 elif singleCall is True: 611 ↛ 614line 611 didn't jump to line 614, because the condition on line 611 was never false

612 finished = True

613 else:

614 offset += len(bytesRead)

615 remainingBytesToRead -= len(bytesRead)

616

617 return data

618

619 def closeFile(self, treeId, fileId):

620 """

621 closes a file handle

622

623 :param HANDLE treeId: a valid handle for the share where the file is to be opened

624 :param HANDLE fileId: a valid handle for the file/directory to be closed

625

626 :return: None, raises a SessionError exception if error.

627

628 """

629 try:

630 return self._SMBConnection.close(treeId, fileId)

631 except (smb.SessionError, smb3.SessionError) as e:

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

633

634 def deleteFile(self, shareName, pathName):

635 """

636 removes a file

637

638 :param string shareName: a valid name for the share where the file is to be deleted

639 :param string pathName: the path name to remove

640

641 :return: None, raises a SessionError exception if error.

642

643 """

644 try:

645 return self._SMBConnection.remove(shareName, pathName)

646 except (smb.SessionError, smb3.SessionError) as e:

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

648

649 def queryInfo(self, treeId, fileId):

650 """

651 queries basic information about an opened file/directory

652

653 :param HANDLE treeId: a valid handle for the share where the file is to be opened

654 :param HANDLE fileId: a valid handle for the file/directory to be closed

655

656 :return: a smb.SMBQueryFileBasicInfo structure. raises a SessionError exception if error.

657

658 """

659 try:

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

661 res = self._SMBConnection.query_file_info(treeId, fileId)

662 else:

663 res = self._SMBConnection.queryInfo(treeId, fileId)

664 return smb.SMBQueryFileStandardInfo(res)

665 except (smb.SessionError, smb3.SessionError) as e:

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

667

668 def createDirectory(self, shareName, pathName ):

669 """

670 creates a directory

671

672 :param string shareName: a valid name for the share where the directory is to be created

673 :param string pathName: the path name or the directory to create

674

675 :return: None, raises a SessionError exception if error.

676

677 """

678 try:

679 return self._SMBConnection.mkdir(shareName, pathName)

680 except (smb.SessionError, smb3.SessionError) as e:

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

682

683 def deleteDirectory(self, shareName, pathName):

684 """

685 deletes a directory

686

687 :param string shareName: a valid name for the share where directory is to be deleted

688 :param string pathName: the path name or the directory to delete

689

690 :return: None, raises a SessionError exception if error.

691

692 """

693 try:

694 return self._SMBConnection.rmdir(shareName, pathName)

695 except (smb.SessionError, smb3.SessionError) as e:

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

697

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

699 """

700 waits for a named pipe

701

702 :param HANDLE treeId: a valid handle for the share where the pipe is

703 :param string pipeName: the pipe name to check

704 :param integer timeout: time to wait for an answer

705

706 :return: None, raises a SessionError exception if error.

707

708 """

709 try:

710 return self._SMBConnection.waitNamedPipe(treeId, pipeName, timeout = timeout)

711 except (smb.SessionError, smb3.SessionError) as e:

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

713

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

715 """

716 writes to a named pipe using a transaction command

717

718 :param HANDLE treeId: a valid handle for the share where the pipe is

719 :param HANDLE fileId: a valid handle for the pipe

720 :param string data: buffer with the data to write

721 :param boolean waitAnswer: whether or not to wait for an answer

722

723 :return: None, raises a SessionError exception if error.

724

725 """

726 try:

727 return self._SMBConnection.TransactNamedPipe(treeId, fileId, data, waitAnswer = waitAnswer)

728 except (smb.SessionError, smb3.SessionError) as e:

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

730

731

732 def transactNamedPipeRecv(self):

733 """

734 reads from a named pipe using a transaction command

735

736 :return: data read, raises a SessionError exception if error.

737

738 """

739 try:

740 return self._SMBConnection.TransactNamedPipeRecv()

741 except (smb.SessionError, smb3.SessionError) as e:

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

743

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

745 """

746 writes to a named pipe

747

748 :param HANDLE treeId: a valid handle for the share where the pipe is

749 :param HANDLE fileId: a valid handle for the pipe

750 :param string data: buffer with the data to write

751 :param boolean waitAnswer: whether or not to wait for an answer

752

753 :return: None, raises a SessionError exception if error.

754

755 """

756 try:

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

758 return self._SMBConnection.write_andx(treeId, fileId, data, wait_answer = waitAnswer, write_pipe_mode = True)

759 else:

760 return self.writeFile(treeId, fileId, data, 0)

761 except (smb.SessionError, smb3.SessionError) as e:

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

763

764

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

766 """

767 read from a named pipe

768

769 :param HANDLE treeId: a valid handle for the share where the pipe resides

770 :param HANDLE fileId: a valid handle for the pipe

771 :param integer bytesToRead: amount of data to read

772

773 :return: None, raises a SessionError exception if error.

774

775 """

776

777 try:

778 return self.readFile(treeId, fileId, bytesToRead = bytesToRead, singleCall = True)

779 except (smb.SessionError, smb3.SessionError) as e:

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

781

782

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

784 """

785 downloads a file

786

787 :param string shareName: name for the share where the file is to be retrieved

788 :param string pathName: the path name to retrieve

789 :param callback callback: function called to write the contents read.

790 :param int shareAccessMode:

791

792 :return: None, raises a SessionError exception if error.

793

794 """

795 try:

796 if shareAccessMode is None: 796 ↛ 800line 796 didn't jump to line 800, because the condition on line 796 was never false

797 # if share access mode is none, let's the underlying API deals with it

798 return self._SMBConnection.retr_file(shareName, pathName, callback)

799 else:

800 return self._SMBConnection.retr_file(shareName, pathName, callback, shareAccessMode=shareAccessMode)

801 except (smb.SessionError, smb3.SessionError) as e:

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

803

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

805 """

806 uploads a file

807

808 :param string shareName: name for the share where the file is to be uploaded

809 :param string pathName: the path name to upload

810 :param callback callback: function called to read the contents to be written.

811 :param int shareAccessMode:

812

813 :return: None, raises a SessionError exception if error.

814

815 """

816 try:

817 if shareAccessMode is None: 817 ↛ 821line 817 didn't jump to line 821, because the condition on line 817 was never false

818 # if share access mode is none, let's the underlying API deals with it

819 return self._SMBConnection.stor_file(shareName, pathName, callback)

820 else:

821 return self._SMBConnection.stor_file(shareName, pathName, callback, shareAccessMode)

822 except (smb.SessionError, smb3.SessionError) as e:

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

824

825 def listSnapshots(self, tid, path):

826 """

827 lists the snapshots for the given directory

828

829 :param int tid: tree id of current connection

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

831 """

832

833 # Verify we're under SMB2+ session

834 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:

835 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)

836

837 fid = self.openFile(tid, path, FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE,

838 fileAttributes=None, creationOption=FILE_SYNCHRONOUS_IO_NONALERT,

839 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE)

840

841 # first send with maxOutputResponse=16 to get the required size

842 try:

843 snapshotData = SRV_SNAPSHOT_ARRAY(self._SMBConnection.ioctl(tid, fid, FSCTL_SRV_ENUMERATE_SNAPSHOTS,

844 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=16))

845 except (smb.SessionError, smb3.SessionError) as e:

846 self.closeFile(tid, fid)

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

848

849 if snapshotData['SnapShotArraySize'] >= 52:

850 # now send an appropriate sized buffer

851 try:

852 snapshotData = SRV_SNAPSHOT_ARRAY(self._SMBConnection.ioctl(tid, fid, FSCTL_SRV_ENUMERATE_SNAPSHOTS,

853 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=snapshotData['SnapShotArraySize']+12))

854 except (smb.SessionError, smb3.SessionError) as e:

855 self.closeFile(tid, fid)

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

857

858 self.closeFile(tid, fid)

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

860

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

862 """

863 creates a mount point at an existing directory

864

865 :param int tid: tree id of current connection

866 :param string path: directory at which to create mount point (must already exist)

867 :param string target: target address of mount point

868 """

869

870 # Verify we're under SMB2+ session

871 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:

872 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)

873

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

875 creationOption=FILE_OPEN_REPARSE_POINT)

876

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

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

879 else:

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

881

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

883

884 reparseData = MOUNT_POINT_REPARSE_DATA_STRUCTURE()

885

886 reparseData['PathBuffer'] = fixed_name + b"\x00\x00" + name + b"\x00\x00"

887 reparseData['SubstituteNameLength'] = len(fixed_name)

888 reparseData['PrintNameOffset'] = len(fixed_name) + 2

889 reparseData['PrintNameLength'] = len(name)

890

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

892 inputBlob=reparseData)

893

894 self.closeFile(tid, fid)

895

896 def removeMountPoint(self, tid, path):

897 """

898 removes a mount point without deleting the underlying directory

899

900 :param int tid: tree id of current connection

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

902 """

903

904 # Verify we're under SMB2+ session

905 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:

906 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)

907

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

909 creationOption=FILE_OPEN_REPARSE_POINT)

910

911 reparseData = MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE()

912

913 reparseData['DataBuffer'] = b""

914

915 try:

916 self._SMBConnection.ioctl(tid, fid, FSCTL_DELETE_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL,

917 inputBlob=reparseData)

918 except (smb.SessionError, smb3.SessionError) as e:

919 self.closeFile(tid, fid)

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

921

922 self.closeFile(tid, fid)

923

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

925 """

926 renames a file/directory

927

928 :param string shareName: name for the share where the files/directories are

929 :param string oldPath: the old path name or the directory/file to rename

930 :param string newPath: the new path name or the directory/file to rename

931

932 :return: True, raises a SessionError exception if error.

933

934 """

935

936 try:

937 return self._SMBConnection.rename(shareName, oldPath, newPath)

938 except (smb.SessionError, smb3.SessionError) as e:

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

940

941 def reconnect(self):

942 """

943 reconnects the SMB object based on the original options and credentials used. Only exception is that

944 manualNegotiate will not be honored.

945 Not only the connection will be created but also a login attempt using the original credentials and

946 method (Kerberos, PtH, etc)

947

948 :return: True, raises a SessionError exception if error

949 """

950 userName, password, domain, lmhash, nthash, aesKey, TGT, TGS = self.getCredentials()

951 self.negotiateSession(self._preferredDialect)

952 if self._doKerberos is True: 952 ↛ 953line 952 didn't jump to line 953, because the condition on line 952 was never true

953 self.kerberosLogin(userName, password, domain, lmhash, nthash, aesKey, self._kdcHost, TGT, TGS, self._useCache)

954 else:

955 self.login(userName, password, domain, lmhash, nthash, self._ntlmFallback)

956

957 return True

958

959 def setTimeout(self, timeout):

960 try:

961 return self._SMBConnection.set_timeout(timeout)

962 except (smb.SessionError, smb3.SessionError) as e:

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

964

965 def getSessionKey(self):

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

967 return self._SMBConnection.get_session_key()

968 else:

969 return self._SMBConnection.getSessionKey()

970

971 def setSessionKey(self, key):

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

973 return self._SMBConnection.set_session_key(key)

974 else:

975 return self._SMBConnection.setSessionKey(key)

976

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

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

979

980 def close(self):

981 """

982 logs off and closes the underlying _NetBIOSSession()

983

984 :return: None

985 """

986 try:

987 self.logoff()

988 except:

989 pass

990 self._SMBConnection.close_session()

991

992 class SessionError(Exception):

993 """

994 This is the exception every client should catch regardless of the underlying

995 SMB version used. We'll take care of that. NETBIOS exceptions are NOT included,

996 since all SMB versions share the same NETBIOS instances.

997 """

998 def __init__( self, error = 0, packet=0):

999 Exception.__init__(self)

1000 self.error = error

1001 self.packet = packet

1002

1003 def getErrorCode( self ):

1004 return self.error

1005

1006 def getErrorPacket( self ):

1007 return self.packet

1008

1009 def getErrorString( self ):

1010 return nt_errors.ERROR_MESSAGES[self.error]

1011

1012 def __str__( self ):

1013 if self.error in nt_errors.ERROR_MESSAGES: 1013 ↛ 1016line 1013 didn't jump to line 1016, because the condition on line 1013 was never false

1014 return 'SMB SessionError: %s(%s)' % (nt_errors.ERROR_MESSAGES[self.error])

1015 else:

1016 return 'SMB SessionError: 0x%x' % self.error